TECH 으로 돌아가기
TECH HACKER NEWS 오늘 6분 읽기 23 READS

std::move 없이도 이동한다 — C++23이 바꾸는 반환값 최적화의 기본값

std::move 없이도 이동한다 — C++23이 바꾸는 반환값 최적화의 기본값
SOURCE IMAGE · HACKER NEWS

C++에서 성능을 갉아먹는 가장 흔한 원인 중 하나는 눈에 잘 띄지 않는 불필요한 복사다. 객체를 함수에서 반환하거나 다른 함수로 넘길 때마다 개발자가 의식하지 못하는 사이에 복사 생성자가 호출될 수 있고, 이런 비용이 누적되면 코드의 의도와 무관하게 프로그램이 느려진다. C++ 트레이너이자 저자인 안드레아스 페르티히(Andreas Fertig)는 앞서 "std::move는 드물게만 쓰라"고 조언한 바 있는데, 이번 글에서는 그 조언을 따랐을 때 오히려 기본적으로 최선의 성능을 얻게 되는 이유를 설명한다. 핵심은 컴파일러와 언어 표준이 이미 상당 부분의 이동·복사 회피를 자동으로 처리해 준다는 점이다.

복사를 아예 없애는 최선의 길, RVO

가장 이상적인 상황은 복사도 이동도 하지 않는 것이다. 반환값 최적화(RVO)는 함수 내부의 스택에 결과 객체를 만든 뒤 호출한 쪽으로 옮기는 대신, 애초에 값이 최종적으로 놓일 호출 지점에 곧바로 객체를 생성한다. 그 결과 복사와 이동이 모두 사라진다. 표준은 이를 컴파일러 최적화가 아니라 '복사 생략(copy elision)'이라는 언어 규칙으로 규정하는데, 표준 문서가 특정 컴파일러 최적화를 직접 언급하지 않기 때문이다. C++17부터는 순수 RVO에 해당하는 경우, 즉 이름 없는 임시 객체를 그대로 반환하는 상황에서 이 복사 생략이 보장된다. 개발자가 무언가를 명시하지 않아도 표준이 결과를 확정해 주는 것이다.

이름이 붙은 지역 변수를 반환하는 명명된 반환값 최적화(NRVO)는 사정이 조금 다르다. NRVO는 C++17의 보장된 복사 생략 대상이 아니다. 다만 실무적으로는 대부분의 컴파일러가 이 경우에도 복사나 이동을 발생시키지 않는다. 즉 보장은 없지만 현실적으로는 거의 항상 최적화가 이뤄진다고 기대할 수 있다. 여기서 얻을 교훈은, 반환할 때 값을 굳이 std::move로 감싸면 오히려 NRVO를 방해해 이동을 강제하게 될 수 있다는 점이다. 가만히 두는 편이 더 빠른 셈이다.

복사 생략 다음의 차선책, 암시적 이동

복사 생략이 불가능할 때 그다음으로 좋은 것은 복사 대신 이동이다. C++11 이후 언어에는 이미 암시적으로 이동이 일어나는 자리가 몇 군데 있었다. 예를 들어 함수 매개변수로 받은 객체를 그대로 반환하면 그 매개변수로부터 결과 객체가 이동된다. 문제는 언어가 처리해 주는 경계가 다소 애매하고 컴파일러마다 동작이 갈리는 경우가 있었다는 점이다.

페르티히가 든 첫 번째 사례는 우측값 참조(rvalue reference) 매개변수를 받아 그 객체를 값으로 반환하는 함수다. 이 코드는 컴파일되지만, C++20 이전에는 반환 시 복사 생성이 일어난다. 흥미롭게도 이는 표준을 충실히 따르는 GCC에서 나타나는 결과이며, 표준을 덜 엄격하게 따르는 Clang은 오히려 이동 생성을 수행한다. 원칙을 곧이곧대로 지키지 않은 쪽이 결과적으로 더 나은, 다소 역설적인 상황인 셈이다. 두 번째 사례는 더 성가시다. 마찬가지로 우측값 참조 매개변수를 받되 이번에는 우측값 참조를 반환하는 함수인데, 이 경우 반환값을 손수 std::move로 이동시키지 않으면 아예 컴파일되지 않는다. std::move를 드물게 쓰라는 조언과 정면으로 부딪히는 상황이다. 공정하게 말하면 두 사례 모두 최선의 결과를 얻으려면 결국 수동 이동이 필요했고, 그런 의미에서 둘 다 저자의 원칙을 거스른다.

C++23이 정리하는 기본값

C++23 모드로 컴파일러를 전환하면 이 두 경우 모두 암시적 이동이 적용된다. std::move를 직접 붙일 필요가 없어지는 것이다. 언어가 이전까지 개발자에게 떠넘겼던 예외적인 자리들을 표준 차원에서 메워 주면서, "std::move는 드물게만"이라는 규칙을 더 폭넓게, 더 안심하고 따를 수 있게 됐다. 실무적으로 이 변화가 주는 함의는 분명하다. 새 표준으로 이동할 여건이 되는 팀이라면 반환문에 습관적으로 std::move를 덧붙이던 관행을 재검토할 만하다. 불필요한 std::move는 RVO/NRVO 같은 복사 생략을 무력화해 오히려 성능을 떨어뜨릴 수 있기 때문이다.

다만 한계도 함께 봐야 한다. 이 이점은 C++23을 실제로 지원하고 해당 모드로 빌드할 때만 온전히 얻어진다. 여전히 C++17이나 C++20에 묶여 있는 프로젝트, 그리고 컴파일러 버전이 제각각인 환경에서는 앞서 본 GCC와 Clang의 동작 차이처럼 표준 준수 수준에 따라 결과가 갈릴 수 있다. 결국 핵심 메시지는 std::move를 무조건 배격하라는 것이 아니라, 언어와 컴파일러가 이미 처리해 주는 영역을 정확히 이해하고 개발자가 개입해야 할 자리를 최소한으로 좁히라는 것이다. 어떤 표준을 쓰는지, 어떤 컴파일러로 빌드하는지를 아는 것이 여전히 성능을 좌우하는 전제 조건이다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://andreasfertig.com/blog/2026/09/move-in-cpp-without-a...
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...