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

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

이 글의 핵심

C++ RVO·NRVO에 대한 실전 가이드입니다.

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

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

C++에서 함수 반환 시 복사가 생략되는 것은 RVO(Return Value Optimization) 덕분입니다.

// ❌ 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 이전: 선택적

// C++14
Data createData() {
    return Data();  // RVO 가능 (선택적)
}

// 컴파일러가 RVO를 안 하면:
// 1. 이동 생성자 호출
// 2. 이동 생성자도 없으면 복사 생성자 호출

C++17 이후: 보장

// 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

// ❌ 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문

// ❌ 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: 참조 반환

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

// ✅ 값 반환
std::string foo() {
    std::string result = "Hello";
    return result;  // ✅ NRVO 가능
}

5. 성능 측정

벤치마크

#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를 쓰지 않는가?
  • 단일 변수를 반환하는가?
  • 참조 반환이 아닌가?
  • 이동 생성자가 구현되어 있는가?

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

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

  • C++ 이동 시맨틱 | std::move 완벽 가이드
  • C++ 복사 생략 | Copy Elision 가이드
  • C++ rvalue 참조 | && 완벽 가이드
  • C++ 이동 생성자 | Move Constructor

마치며

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

핵심 원칙:

  1. return에 std::move 쓰지 마세요
  2. 단일 변수 반환
  3. C++17부터 RVO 보장

return std::move(local)RVO를 방해하므로 절대 쓰지 마세요.

다음 단계: RVO를 이해했다면, C++ 이동 시맨틱 가이드에서 더 깊이 배워보세요.


관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |