ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Guaranteed Copy Elison
    개발/C·C++ 2021. 6. 27. 18:15

    >> 번역글입니다

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

    >> 출처 https://jonasdevlieghere.com/guaranteed-copy-elision/

     

    Copy Elision

    C++17 표준의 변화를 논의하기 전에, C++14 표준에 정의되어 있던 복사 생략의 기본을 다시 살펴보는 것이 도움이 될 수 있습니다. 복사 생략이 무엇이고 어떻게 동작하는지 안다면 이 파트는 건너뛰어도 됩니다.

     

    Foo 구조체는 모든 예제에서 사용됩니다. 생성자와 소멸자가 호출될 때마다 해당 정보들이 출력될 것입니다.

    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; }
    };

     

    Return Value Optimization

    복사 생략의 가장 일반적인 기술은 반환 값 최적화입니다(return value optimization). 값으로 객체를 반환하면 컴파일러는 복사를 피할 수 있습니다. 표준 문서에 다음과 같은 문구가 있습니다.

    클래스 반환 타입이 있는 함수의 리턴문에서, 표현식이 함수 리턴 타입과 동일한 cv-unqualified type인 non-volatile 자동 객체(스택에 생성되는 지역 변수와 같은 개념)라면, 자동 객체를 함수의 반환 값에 직접 생성해 복사/이동을 생략할 수 있다.

     

    Named Return Value Optimization(NRVO)

    어느 쪽이든 최적화가 적용되지만 일반적인 RVO와 이름 있는 RVO는 구분됩니다. 아래의 예제는 이름 있는 값인 foo를 반환합니다.

    Foo f() 
    {
      Foo foo;
      return foo;
    }
    int main() { Foo foo = f(); }

     

    Return Value Optimization(RVO)

    일반적인 경우의 RVO는 아래와 같습니다. 단순하게 임시 값을 반환합니다.

    Foo f() 
    {
      return Foo();
    }

    MSVC에서는 복사 생략을 끌 수 없지만, clang에서는 -fno-elide-constructors 옵션을 통해 컴파일러가 복사 생략을 수행하지 않도록 할 수 있습니다. gcc에서도 같은 플래그를 사용할 수 있습니다. 아래의 결과는 RVO와 NRVO 모두에서 동일합니다.

    $ clang++ foo.cpp -std=c++11 -fno-elide-constructors && ./a.out
    Constructed
    Move-constructed
    Destructed
    Move-constructed
    Destructed
    Destructed
    
    $ clang++ foo.cpp -std=c++11 && ./a.out
    Constructed
    Destructed

    최적화는 두 번의 (이동)생성자 호출을 절약해줍니다. 첫 번째는 Foo 구조체의 지역 객체를 f() 함수의 반환 값으로 복사하는 것이고, 두 번째는 함수가 반환하는 임시 Foo 객체를 메인 함수의 foo로 복사하는 것입니다.

     

    Passing a Temporary by Value

    두 번째 일반적인 경우는 임시 값을 넘기는 것입니다. 표준 문서에 다음과 같은 문구가 있습니다.

    참조에 바인딩되지 않은 임시 클래스 객체가 동일한 cv-unqualified 타입의 클래스 객체로 복사/이동될 때, 임시 객체를 직접 생성하여 복사/이동 연산을 생략할 수 있다
    void f(Foo f) { std::cout << "Fn" << std::endl; }
    
    int main() 
    {
      f(Foo());
    }
    $ clang++ foo.cpp -fno-elide-constructors -std=c++11 && ./a.out
    Constructed
    Move-constructed
    Fn
    Destructed
    Destructed
    
    $ clang++ foo.cpp -std=c++11 && ./a.out
    Constructed
    Fn
    Destructed

     

    Guaranteed Copy Elison

    보장된 복사 생략 제안은 현재 상황과 관련된 몇 가지 문제를 강조합니다. 주된 문제는, 보장된 생략 없이는 이동/복사 생성자를 삭제할 수 없습니다. 생략이 일어나지 않을 수 있기 때문입니다. 이는 팩토리처럼, 이동할 수 없는 값을 반환하는 함수를 가지지 못하게 합니다. 아래 예제는 컴파일되지 않습니다. 앞의 섹션에서, 복사/이동 생성자가 필요 없는 복사 생략을 봤는데도 말이죠.

    struct Foo 
    {
      Foo() { std::cout << "Constructed" << std::endl; }
      Foo(const Foo &) = delete;
      Foo(const Foo &&) = delete;
      ~Foo() { std::cout << "Destructed" << std::endl; }
    };
    
    Foo f() 
    {
      return Foo();
    }
    
    int main() 
    {
      Foo foo = f();
    }

    C++ 17에서는 컴파일이 잘 되며 만약 이동/복사 생성자가 있어도 같은 결과를 출력합니다. 이것이 동작하는 방법은 다음에서 설명됩니다.

     

    Value Categories

    이 제안의 완전한 이름은 "단순화된 값 범주를 통한 복사 생략의 보장"입니다. 보장된 복사 생략을 위해, prvalue(pure rvalue) 표현식과 그들에 의해 초기화된 임시 객체를 구분해야 합니다. 더 구체적으로 말하면, glvalue(generalized lvalue)는 객체의 위치로 정의되며, prvalue는 객체의 이니셜라이저로 정의됩니다.

     

    만약 prvalue가 같은 타입 객체인 이니셜라이저로 사용된다면, 직접 초기화를 수행합니다. 결과적으로, 함수의 반환 값을 초기화하면 복사/이동 없이 직접 초기화됩니다. 이는 객체의 복사/이동 생성자에 접근할 필요가 없다는 의미입니다. 두 번째 결과는 C++17의 보장된 복사 생략이 NRVO에 대해선 바뀐 것이 없다는 것입니다. 앞서 말했듯이, 변화는 prvalue에 관해서만 이루어졌습니다. NRVO에서 이름 있는 값은 glvalue입니다.

    '개발 > C·C++' 카테고리의 다른 글

    [Template] 템플릿 구체화  (0) 2021.07.05
    [Priority queue] 간단한 참고  (0) 2021.07.03
    InterlockedExchange, InterlockedCompareExchange  (0) 2021.06.10
    timeBeginPeriod function  (0) 2021.06.10
    WSASend function  (0) 2021.06.06

    댓글

Designed by Tistory.