콘텐츠로 이동

Copy Elision & RVO/NRVO

복사 생략은 컴파일러가 불필요한 복사/이동 생성자 호출을 제거하는 최적화입니다. 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 OK
auto x = make(); // 복사/이동 없이 직접 생성

NRVO — 이름 있는 지역 변수 반환

섹션 제목: “NRVO — 이름 있는 지역 변수 반환”

이름 있는 지역 변수를 반환하는 경우로, C++17에서도 보장되지 않으며 컴파일러 재량입니다.

std::string make(bool flag) {
std::string result;
result = flag ? "hello" : "world";
return result; // NRVO 적용 가능
}

NRVO가 적용되면 result가 호출자의 메모리 공간에 직접 생성됩니다. 미적용 시 이동 생성자가 호출됩니다.

// 여러 변수를 조건부로 반환 → 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" 만 출력
}
// 잘못된 코드 — 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 방해 — 지양