[2026] C++ RVO·NRVO | 복사 생략 최적화와 성능 향상

[2026] C++ RVO·NRVO | 복사 생략 최적화와 성능 향상

이 글의 핵심

C++ RVO·NRVO의 C++, RVO·NRVO, 복사, 들어가며: return에 std::move를 써야 하나요?를 실전 예제와 함께 상세히 설명합니다.

들어가며: “return에 std::move를 써야 하나요?"

"함수 반환 시 복사가 일어나지 않아요”

C++에서 함수 반환 시 복사가 생략되는 것은 RVO(Return Value Optimization) 덕분입니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ std::move 불필요
std::string foo() {
    std::string result = "Hello";
    return std::move(result);  // ❌ RVO 방해
}
// ✅ RVO 활용
std::string foo() {
    std::string result = "Hello";
    return result;  // ✅ 복사 생략
}

이 글에서 다루는 것:

  • RVO와 NRVO
  • C++17 복사 생략 보장
  • std::move 실수
  • 성능 측정

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. RVO란?
  2. NRVO란?
  3. C++17 복사 생략 보장
  4. std::move 실수
  5. 성능 측정
  6. 정리

1. RVO란?

RVO (Return Value Optimization)

RVO함수 반환 시 복사를 생략하는 최적화입니다.

struct Data {
    std::vector<int> vec;
    
    Data() {
        std::cout << "Constructor\n";
    }
    
    Data(const Data&) {
        std::cout << "Copy Constructor\n";
    }
    
    Data(Data&&) noexcept {
        std::cout << "Move Constructor\n";
    }
};
// RVO 예시
Data createData() {
    return Data();  // 임시 객체 반환
}
int main() {
    Data d = createData();
    // 출력: Constructor (복사/이동 없음!)
}

RVO 조건:

  • 임시 객체 (prvalue) 반환
  • C++17부터 보장

2. NRVO란?

NRVO (Named Return Value Optimization)

NRVO이름 있는 지역 변수 반환 시 복사를 생략합니다.

// NRVO 예시
Data createData() {
    Data result;  // 이름 있는 변수
    result.vec.push_back(42);
    return result;  // NRVO 가능
}
int main() {
    Data d = createData();
    // 출력: Constructor (복사/이동 없음!)
}

NRVO 조건:

  • 이름 있는 지역 변수 반환
  • 모든 return문이 같은 변수 반환
  • C++17에서도 선택적 (보장 안 됨)

3. C++17 복사 생략 보장

C++17 이전: 선택적

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// C++14
Data createData() {
    return Data();  // RVO 가능 (선택적)
}
// 컴파일러가 RVO를 안 하면:
// 1. 이동 생성자 호출
// 2. 이동 생성자도 없으면 복사 생성자 호출

C++17 이후: 보장

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C++17
Data createData() {
    return Data();  // RVO 보장
}
// 이동/복사 생성자가 없어도 OK
struct NonMovable {
    NonMovable() = default;
    NonMovable(const NonMovable&) = delete;
    NonMovable(NonMovable&&) = delete;
};
NonMovable createNonMovable() {
    return NonMovable();  // ✅ C++17: OK
}

4. std::move 실수

실수 1: return에 std::move

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ RVO 방해
std::string foo() {
    std::string result = "Hello";
    return std::move(result);  // ❌ RVO 불가
}
// ✅ RVO 활용
std::string foo() {
    std::string result = "Hello";
    return result;  // ✅ NRVO 가능
}

이유: std::movervalue 참조로 변환하므로 NRVO 조건 위반.

실수 2: 여러 return문

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ NRVO 불가
std::string foo(bool flag) {
    std::string a = "A";
    std::string b = "B";
    
    if (flag) {
        return a;  // a 반환
    } else {
        return b;  // b 반환 (다른 변수!)
    }
}
// NRVO 조건 위반: 여러 변수 반환
// 이동 생성자 호출

실수 3: 참조 반환

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ RVO 불가
std::string& foo() {
    std::string result = "Hello";
    return result;  // ❌ 댕글링 참조
}
// ✅ 값 반환
std::string foo() {
    std::string result = "Hello";
    return result;  // ✅ NRVO 가능
}

5. 성능 측정

벤치마크

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <benchmark/benchmark.h>
struct Data {
    std::vector<int> vec;
    Data() : vec(1000000, 42) {}
};
// RVO
static void BM_RVO(benchmark::State& state) {
    for (auto _ : state) {
        Data d =  { return Data(); }();
        benchmark::DoNotOptimize(d);
    }
}
BENCHMARK(BM_RVO);
// std::move (RVO 방해)
static void BM_Move(benchmark::State& state) {
    for (auto _ : state) {
        Data d =  {
            Data result;
            return std::move(result);
        }();
        benchmark::DoNotOptimize(d);
    }
}
BENCHMARK(BM_Move);

결과 (GCC 13, -O3):

BM_RVO     1000000 ns
BM_Move    1500000 ns  (50% 느림)

정리

RVO vs NRVO

항목RVONRVO
반환 대상임시 객체이름 있는 변수
C++17 보장❌ (선택적)
조건prvalue단일 변수

핵심 규칙

  1. return에 std::move 쓰지 마세요 (RVO 방해)
  2. 단일 변수 반환 (NRVO 활성화)
  3. C++17부터 RVO 보장
  4. 이동 생성자 구현 (NRVO 실패 대비)

체크리스트

  • return에 std::move를 쓰지 않는가?
  • 단일 변수를 반환하는가?
  • 참조 반환이 아닌가?
  • 이동 생성자가 구현되어 있는가?

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


마치며

RVO함수 반환 시 복사를 생략하는 강력한 최적화입니다. 핵심 원칙:

  1. return에 std::move 쓰지 마세요
  2. 단일 변수 반환
  3. C++17부터 RVO 보장 return std::move(local)RVO를 방해하므로 절대 쓰지 마세요. 다음 단계: RVO를 이해했다면, C++ 이동 시맨틱 가이드에서 더 깊이 배워보세요.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3