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란?
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::move는 rvalue 참조로 변환하므로 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
| 항목 | RVO | NRVO |
|---|---|---|
| 반환 대상 | 임시 객체 | 이름 있는 변수 |
| C++17 보장 | ✅ | ❌ (선택적) |
| 조건 | prvalue | 단일 변수 |
핵심 규칙
- return에 std::move 쓰지 마세요 (RVO 방해)
- 단일 변수 반환 (NRVO 활성화)
- C++17부터 RVO 보장
- 이동 생성자 구현 (NRVO 실패 대비)
체크리스트
- return에 std::move를 쓰지 않는가?
- 단일 변수를 반환하는가?
- 참조 반환이 아닌가?
- 이동 생성자가 구현되어 있는가?
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 이동 시맨틱 | std::move 완벽 가이드
- C++ 복사 생략 | Copy Elision 가이드
- C++ rvalue 참조 | && 완벽 가이드
- C++ 이동 생성자 | Move Constructor
마치며
RVO는 함수 반환 시 복사를 생략하는 강력한 최적화입니다.
핵심 원칙:
- return에 std::move 쓰지 마세요
- 단일 변수 반환
- C++17부터 RVO 보장
return std::move(local)은 RVO를 방해하므로 절대 쓰지 마세요.
다음 단계: RVO를 이해했다면, C++ 이동 시맨틱 가이드에서 더 깊이 배워보세요.
관련 글
- C++ 시리즈 전체 보기
- C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
- C++ ADL |
- C++ Aggregate Initialization |