[R value] Copy Elision RVO NRVO - ChoiChiWon/ccw GitHub Wiki

copy elision

  • 컴파일러가 불필요한 생성자 호출을 제외시켜 주는 컴파일러 최적화 옵션이다.

Copy elision - Wikipedia

  • C++ 표준에서는 언어 표준이 요구한대로 구현된다면 어떤 최적화를 해도 좋다고하고 있다.
  • 하지만 프로그램의 동작이 변경 될 수 있음에도 불구하고, 여전히 복사가 생략되는 상황이 몇 가지 있다.
  • 대표적인 것이 반환 값 최적화이며, 다른 하나는 클래스 타입의 임시 객체가 동일한 타입의 개체로 복사 할 때의 최적화이다.
int n = 0;
struct C {
  explicit C(int) {}
  C(const C&) { ++n; } // the copy constructor has a visible side effect
};                     // it modifies an object with static storage duration

int main() {
  C c1(42); // direct-initialization, calls C::C(42)
  C c2 = C(42); // copy-initialization, calls C::C( C(42) )
  
  std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
  return 0;
}

// result :
// 0

c++17에서의 Guaranteed Copy Elision

Guaranteed Copy Elision

  • 이동이 불가능한 타입에 대해서는 Copy Elision이 적용되지 않았다.
  • 결과적으로, 내용상 Copy Elision이 가능함에도 이동이 불가능한 타입에 대해서 컴파일 에러 발생하는 문제가 있었다.
  • C++17에서는 Non-movable 객체에 대해서도 Copy Elision이 정상 동작함을 보장하는 것이다.
  • 이것이 C++17의 "Guaranteed Copy Elision"의 핵심적인 내용이다.
struct Foo
{
    Foo() { std::cout << "Constructed\n"; }
    Foo(const Foo &) = delete;
    Foo(const Foo &&) = delete;
 
    ~Foo() { std::cout << "Destructed\n"; }
};
 
Foo f()
{
    // 컴파일 에러
    // error C2280: 'Foo::Foo(const Foo &&)': 삭제된 함수를 참조하려고 합니다.
    return Foo();
}
 
int main()
{
    Foo foo = f();
}

RVO / NRVO(Named RVO)

  • RVO/NRVO는 함수의 반환값이 특정 객체의 값 형식일 때 복사 생성을 회피할 수 있도록 컴파일러 최적화를 의미한다.
  • 즉 임시 객체 생성을 하지 않는다.
  • NRVO는 이름이 있는 변수에 대해서도 RVO가 적용되는 것이다.
  • vistual studio 2005 부터 지원한다.
  • RVO와는 다르게 NRVO는 최적화 옵션 /O1(크기 최소화)부터 동작한다.
    -- Debug 모드에서는 NRVO가 작동하지 않는다.
    -- RVO와 다르게 변수를 선언하고, 해당 변수를 사용한 다음 반환해도 임시 객체가 생성되지 않는다.

Named Return Value Optimization in Visual C++ 2005 - microsoft

class RVO
{
public:
       
   RVO(){printf("I am in constructor\n");}
   RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
   ~RVO(){printf ("I am in destructor\n");}
   int mem_var;       
};
RVO MyMethod (int i)
{
   RVO rvo;
   rvo.mem_var = i;
   return (rvo);
}
int main()
{
   RVO rvo;
   rvo=MyMethod(5);
}

Without NRVO (cl /Od sample1.cpp), the expected output would be:
I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor


With NRVO (cl /O2 sample1.cpp), the expected output would be:
I am in constructor
I am in constructor
I am in destructor
I am in destructor

RVO 가 적용되지 않는 경우

  • 조건에 의해 반환값이 다른 경우
Foo f()
{
   Foo a;
   
   if (true)
      return a;
   else 
      return nullptr;
}
  • std::move를 통해서 객체를 반환하려는 경우
    -- 이동 생성자가 구현되어 있다면 이동 생성자가 호출된다.
Foo f()
{
   Foo a;
   return std::move(a);
}
  • static 선언
    -- 정적 선언 같은 경우 data 영역 메모리에 공간을 잡고 반환 메모리 공간에 복사하기 때문에 복사 생성자가 호출된다.
Foo f()
{
   static Foo a;
   return a;
}

참고 사이트

What are copy elision and return value optimization? - stackoverflow
RVO Setup and Mechanism
RVO V.S. std::move