본문 바로가기
C++/Effective Modern C++

C++11에서 이동 의미론이 항상 도움이 될까

by COCO1337 2020. 7. 28.

결론부터 말하자면 아니다.

아마도 C++ 에서 가장 주된 기능은 이동 의미론일 것이다. 이로 인해 컴파일러는 비싼 복사 연산을 비교적 저렴한 이동 연산으로 대체할 수 있을 뿐 아니라 적절한 조건이 만족되면 반드시 대체해야 한다.

하지만 이동의미론을 지원하지 않는 형식들이 많고, 사용자 정의 형식들은 C++11에 완전히 맞게 수정되지 않았다면 컴파일러가 이동을 지원해도 응용 프로그램의 성능이 저절로 높아지지는 않는다. 이동을 명시적으로 지원하더라도 성능상의 이득이 크지 않을 수 있다. 컨테이너의 내용을 저렴하게 이동하는 방법이 없을 수도 있고, 컨테이너가 제공하는 저렴한 이동연산이 요구하는 까다로운 조건을 컨테이너 요소들이 만족하지 못하는 경우도 있을 수 있다.

그 예로 std::array가 있다.

std::array는 내장배열에 STL인터페이스를 씌운 것이다. 일반적인 컨테이너는 힙에 저장되며 자료 멤버에 그 내용이 저장된 힙 메모리를 가리키는 포인터만 담는다. 그래서 해당 컨테이너를 가리키는 포인터만 이동하면 되기 때문에 상수 시간 이동이 가능하다. 하지만 std::array 객체의 경우 그런 포인터가 없다. std::array의 내용은 std::array 객체 자체에 직접 저장되기 때문이다. 그렇기 때문에 이동과 복사 모두 모든 요소를 일일이 이동/복사 해야 하기 때문에 계산 복잡도가 선형이며, 기존에 컨테이너의 이동이 복사만큼 저렴하다는 것과는 거리가 있다.

std::string은 상수 시간 이동과 선형 시간 복사를 제공한다. 이렇게 생각하면 이동이 복사보다 빠를 것 같지만 항상 그렇지는 않다. 문자열 구현 중에는 작은 문자열 최적화(small string optimization, SSO)를 사용하는 것이 많다. 그런 구현은 작은 문자열을 std::string  객체 안의 버퍼에 넣고 힙은 사용하지 않는다 이런 경우 또한 이동이 복사보다 빠르지 않다.

빠른 이동 연산을 지원하는 형식에도 사실은 복사가 일어나는 경우가 생기기도 한다. 표준 라이브러리의 일부 컨테이너들은 강한 예외 안전성을 보장하며, 그 보장에 의존하는 C++09 코드를 C++11 에서 컴파일해도 코드가 망가지지 않게 복사 연산들을 이동연산들로 대체한다. 때문에 해당 복사연산보다 효율적인 이동연산을 제공하는 형식이라 해도 noexcept로 선언되어 있지 않으면 복사 연산을 호출할 수 있다.


이동 의미론이 도움이 안되는 몇가지 시나리오들이다.

- 이동 연산이 없다 : 이동 연산을 제공하지 않는다. 이 경우 이동 요청은 복사 요청이 된다.

- 이동이 더 빠르지 않다 : 이동할 객체의 이동 연산이 해당 복사연산보다 빠르지 않다.

- 이동을 사용할 수 없다 : 이동이 일어나려면 이동 연산이 예외를 방출하지 않아야 하는 문맥에서 해당 연산이 noexcept로 선언되어 있지 않다.

- 원본 객체가 왼값이다. : 오직 오른값만 이동 연산의 원본이 되는 경우도 있다.

 

하지만 코드가 사용하는 구체적인 형식들을 미리 알수 있고, 형식들의 특징이 바뀌지 않으리라고 확신할 수 있는 경우, 복사보다 저렴한 이동 연산을 제공한다면 이동 의미론 덕분에 복사 연산들이 좀더 저렴한 이동 연산들로 대체될 것이라고 믿어도 안전하다


Conclusion

- 이동 연산들이 존재하지 않고, 저렴하지 않고, 적용되지 않을 것이라고 가정하라

- 형식들과 이동 의미론 지원 여부를 미리 알 수 있는 경우에는 그런 가정을 둘 필요가 없다.


Reference

Effective Modern C++ 항목 29: 이동 연산이 존재하지 않고, 저렴하지 않고, 적용되지 않는다고 가정하라

반응형

댓글