[C++] std::move return은 정말 필요할까?
개요
C++11부터의 중요 화두인 R-value reference를 보고 나서 '앞으로 모든 return 에 std::move를 명시해야 하는가?' 란 의문이 생기게 되었습니다. 앞으로 기존에 단순 return 하는 함수들에도 이를 써야 하나 라는 생각을 했는데 이번에 이에 대해서 알아본 결과를 한번 적어보겠습니다.
일단 return std::move() 는 불필요하다
일단 알아본 바로는 move로 반환할 이유가 없다는 것입니다.
먼저 일반적으로 반환하는 경우 컴파일러가 자동적으로 R-value로 변경을 해 줘서 이후 함수 반환값을 통해서 이동 생성자를 부를 수 있다고 합니다. 다만 std::move를 활용하게 되면 컴파일러가 이를 최적화할 여지가 사라진다고 합니다.
RVO, NRVO와 copy elision
RVO 는 Return Value Optimization
NRVO는 Named Return Value Optimization
elision은 생략
위 return move와 관련한 부분을 알아보면 이런 개념들이 나오게 됩니다. 컴파일러가 알아서 반환값에 대한 최적화를 하는 것인데요. 이러한 개념으로 인해서 컴파일러가 반환값을 r-value로 반환을 해주게 되면서 move를 명시해 줄 필요가 없다는 것입니다.
이 경우를 제가 비주얼 스튜디오에서 한번 확인을 해 봤습니다.
#include <iostream>
struct Foo
{
Foo() { std::cout << "Constructed" << std::endl; }
Foo(const Foo&) { std::cout << "Copy-constructed" << std::endl; }
Foo(Foo&&) { std::cout << "Move-constructed" << std::endl; }
~Foo() { std::cout << "Destructed" << std::endl; }
};
Foo Func()
{
Foo f;
std::cout << &f << '\n';
return f;
}
int main()
{
Foo f1{ Func() };
std::cout << &f1 << '\n';
return 0;
}
여러가지 장난을 쳐본 결과 일단 Debug 모드에서는 move constructor가 불렸습니다. Release 모드에서는 이와 관련한 RVO, NRVO가 켜져 있다고 했는데 이 덕분인지 Foo에서는 Constructored, Destructored만 불리게 되었습니다.

보시는 것처럼 Debug 모드에서는 주소값도 다른 모습을 볼 수 있는데

Release 모드에서는 최적화를 해줘서 주소값도 그렇고 한결 간결한 모습을 볼 수 있습니다.
그럼 이번 의문인 std::move를 적용하면 어떻게 될까요?
Foo Func()
{
Foo f;
std::cout << &f << '\n';
return std::move(f);
}


둘 다 뭔가 이상한 모습을 볼 수 있습니다. 이 경우 이동 생성자를 통해서 모두 처리되는 모습을 볼 수 있습니다. 결국 RVO / NRVO 둘 다 일어나지 않은 모습을 볼 수 있습니다. 최신의 컴파일러일수록 이런 부분의 최적화는 제대로 이루어지고 있으며 함수 내 지역변수에 대해서 move를 해야 할 필요성이 점점 없습니다.

당장 비주얼 2022 버전에서 돌려보면 저렇게 쓰지 말라는 경고가 나오는걸 볼 수 있습니다.