Copy Elision & RVO/NRVO
복사 생략(Copy Elision)이란
섹션 제목: “복사 생략(Copy Elision)이란”복사 생략은 컴파일러가 불필요한 복사/이동 생성자 호출을 제거하는 최적화입니다. C++17부터 일부 경우가 선택적 최적화에서 **필수 규칙(mandatory elision)**으로 격상됐습니다.
RVO — 이름 없는 임시 객체 반환
섹션 제목: “RVO — 이름 없는 임시 객체 반환”함수가 prvalue(이름 없는 임시 객체)를 반환할 때 발생합니다.
std::string make() { return std::string("hello"); // prvalue 반환}auto s = make(); // 호출자의 s 메모리에 직접 생성C++17에서는 이 경우가 보장된 복사 생략입니다. 복사/이동 생성자가 delete되어 있어도 동작합니다.
struct NoCopy { NoCopy() = default; NoCopy(const NoCopy&) = delete; NoCopy(NoCopy&&) = delete;};
NoCopy make() { return NoCopy{}; } // C++17 OKauto x = make(); // 복사/이동 없이 직접 생성NRVO — 이름 있는 지역 변수 반환
섹션 제목: “NRVO — 이름 있는 지역 변수 반환”이름 있는 지역 변수를 반환하는 경우로, C++17에서도 보장되지 않으며 컴파일러 재량입니다.
std::string make(bool flag) { std::string result; result = flag ? "hello" : "world"; return result; // NRVO 적용 가능}NRVO가 적용되면 result가 호출자의 메모리 공간에 직접 생성됩니다. 미적용 시 이동 생성자가 호출됩니다.
NRVO가 방해받는 패턴
섹션 제목: “NRVO가 방해받는 패턴”// 여러 변수를 조건부로 반환 → NRVO 불가std::string get(int n) { std::string a = "hello"; std::string b = "world"; if (n > 0) return a; return b;}
// 조건 연산자에서 서로 다른 표현식 반환std::string get2(int n) { std::string r; return n > 0 ? r : std::string("default");}단일 반환 경로와 단일 지역 변수가 NRVO에 유리합니다.
동작 확인
섹션 제목: “동작 확인”struct Trace { Trace() { puts("ctor"); } Trace(const Trace&) { puts("copy"); } Trace(Trace&&) { puts("move"); } ~Trace() { puts("dtor"); }};
Trace make() { return Trace{}; }
int main() { auto t = make(); // RVO 적용 시: "ctor" / "dtor" 만 출력}return std::move(x) 안티패턴
섹션 제목: “return std::move(x) 안티패턴”// 잘못된 코드 — NRVO를 방해하고 이동만 보장std::string bad() { std::string s = "hello"; return std::move(s); // NRVO 차단, 이동 생성자 강제 호출}
// 올바른 코드std::string good() { std::string s = "hello"; return s; // NRVO 기회 제공, 실패 시 암묵적 이동}return std::move(s)는 NRVO 최적화를 막습니다. 단순히 return s;로 작성하면 NRVO가 적용되거나, 미적용 시에도 암묵적 이동이 발생합니다.
| 케이스 | C++17 보장 | 비고 |
|---|---|---|
| prvalue 반환 (RVO) | ✅ 보장 | 복사/이동 생성자 불필요 |
| 이름 있는 변수 반환 (NRVO) | ❌ 재량 | 단일 경로에서 대부분 적용 |
return std::move(x) | ❌ 역효과 | NRVO 방해 — 지양 |