ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Rvalue reference] Forcing Move Semantics(4)
    개발/C·C++ 2021. 8. 26. 20:15

    >> 번역글입니다

    >> 의역은 있고 오역이 있을 수 있습니다

    >> 출처 http://thbecker.net/articles/rvalue_references/section_04.html

     

    C++ 표준의 수정 조항 1조는 "위원회는 C++ 개발자가 자신의 발에 총 쏘는 것을 금지하는 규칙을 만들지 않을 것이다"라고 명시되어 있습니다. 농담을 조금 빼고 말하면, 개발자에게 제어를 더 주는 것과 그들을 부주의에서 지켜주는 것을 논할 때, C++은 제어를 더 제공하는 실수를 범하는 경향이 있습니다. 그 정신에 입각해, C++11은 당신이 rvalue뿐만 아니라, 재량에 따라서 lvalue에도 이동 의미론을 사용할 수 있습니다. 한 가지 좋은 예가 std 라이브러리의 swap 함수입니다. 이전과 마찬가지로, rvalue에 대해 이동 의미론을 수행하기 위해 복사 생성자와 복사 할당 연산자를 오버로딩한 X클래스를 가정하겠습니다.

    template<class T>
    void swap(T& a, T& b) 
    { 
      T tmp(a);
      a = b; 
      b = tmp; 
    } 
    
    X a, b;
    swap(a, b);

    여기에 rvalue는 없습니다. 그러므로 swap 함수의 세 줄은 이동 의미론을 사용하지 않습니다. 하지만 우리는 변수가 복사 생성자나 복사 할당 연산자의 소스로 등장할 때, 그 변수가 다시는 사용되지 않거나 할당할 때 한 번만 사용되는 경우에 이동 의미론을 써도 괜찮다는 걸 알고 있습니다.

     

    C++11에서는 우리를 구해주기 위해 등장한 std::move 함수가 있습니다. 이 함수는 다른 일은 하지 않고 인수를 rvalue로 변환해줍니다. C++11에서, std 라이브리 함수 swap은 다음처럼 생겼습니다.

    template<class T> 
    void swap(T& a, T& b) 
    { 
      T tmp(std::move(a));
      a = std::move(b); 
      b = std::move(tmp);
    } 
    
    X a, b;
    swap(a, b);

    이제 swap 함수의 세 줄 모두 이동 의미론을 사용합니다. 이동 의미론을 구현하지 않은 타입이라면(rvalue 레퍼런스 버전으로 복사 생성자와 할당 연산자를 오버로드하지 말 것), 새로운 swap 함수는 그 전 함수와 똑같이 작동할 겁니다. std::move 함수는 매우 간단한 함수입니다. 하지만 불행하게도 구현 부분을 아직 보여줄 수 없습니다. 나중에 다룰 겁니다.

     

    swap 함수에서 보여줬던 것처럼, 어디서나 std::move를 사용하는 것은 다음의 중요한 이익을 얻을 수 있습니다.

    • 이동 의미론을 구현하는 타입에 대해, 많은 표즌 알고리즘과 동작은 이동 의미론을 사용할 것이며 잠재적으로 상당한 성능 이점을 얻습니다. 한 가지 중요한 예는, 제자리 정렬입니다. 제자리 정렬 알고리즘은 다른 일은 하지 않고 요소들을 스왑합니다. 그리고 스와핑은 이동 의미론을 제공하는 타입에 대해 이동 의미론을 활용할 것입니다.
    • STL은 가끔 특정 타입의 복사 가능성(copyability)을 요구합니다. 예를 들어 타입은 컨테이너의 요소로 사용될 수 있습니다. 자세하게 조사했을 때, 많은 경우 이동 가능성(moveability)으로 충분합니다. 그러므로 우리는 과거에는 허용하지 않았지만, 복사 가능성이 아니라(unique_pointer가 생각 나네요) 이동 가능성인 타입을 사용할 수 있습니다. 예를 들어 이러한 타입은 STL 컨테이너 요소로서 사용될 수 있습니다.

    std::move에 대해 알고 있는 지금, 우리는 (3)번 아티클에서 보인 복사 할당 연산자의 rvalue 레퍼런스 오버로드의 구현이 여전히 문제가 있는 이유를 살펴봐야 하는 상태입니다. 변수 사이의 간단한 할당을 생각해보죠.

    a = b;

    여기에서 무슨 일이 일어날 것이라고 생각하나요? a가 갖고 있는 객체가 b의 복사에 의해서 대체되고, 당연하게도, a가 갖고 있던 객체가 파괴될 것이라고 예상할 겁니다. 자, 다음 줄을 봅시다.

    a = std::move(b)

    만약 이동 의미론이 단순한 교환(simple swap)으로 구현되어 있다면, a와 b가 지니고 있는 객체가 서로 교환되는 효과가 있을 겁니다. 아직 아무 것도 파괴되지 않았습니다. 물론 a가 이전에 갖고 있던 객체는 b가 스코프를 벗어나면 결국 파괴되겠죠. b가 이동의 타겟이 되지 않는다면, 이전에 a가 갖고 있던 객체는 다시 전달될 것입니다. 그러므로 복사 할당자의 구현자가 관련되어 있는 한, 이전에 a가 가지고 있던 객체가 언제 파괴될지 알 수 없습니다.

     

    우리는 결정되지 않는 파괴의 지옥으로 빠져 버렸습니다. 하나의 변수가 할당되었고 그 변수가 갖고 있던 객체는 여전히 어딘가에 존재하고 있는 것이죠. 그 객체가 파괴되는 것이 밖에서 볼 때 부작용이 없다면 괜찮긴 합니다. 하지만 때때로 소멸자는 부작용을 갖습니다. 소멸자 내부에서 락이 해제되는 경우입니다. 그러므로 객체 소멸이 부작용을 갖는 부분은 복사 할당 연산자의 rvalue 레퍼런스 오버로드에서 명시적으로 수행되어야 합니다.

    X& X::operator=(X&& rhs)
    {
    
      // 소멸자가 부작용을 갖는 부분에 대해 주의하면서 뒷정리를 수행하세요
      // 객체를 파괴 가능하고 할당 가능한 상태로 두세요.
    
      // 이동 의미론: this와 rhs 사이의 내용을 교환  
      return *this;
    }

    댓글

Designed by Tistory.