development

C ++ 11 rvalue 및 이동 의미 론적 혼란 (return statement)

big-blog 2020. 2. 20. 23:33
반응형

C ++ 11 rvalue 및 이동 의미 론적 혼란 (return statement)


rvalue 참조를 이해하고 C ++ 11의 의미를 이동하려고합니다.

이 예제의 차이점은 무엇이며 어떤 것들은 벡터 복사를하지 않습니까?

첫 번째 예

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

두 번째 예

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

세 번째 예

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

첫 번째 예

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

첫 번째 예는에 의해 발견 된 임시를 리턴합니다 rval_ref. 그 임시적인 수명은 rval_ref정의를 넘어 연장 되며 마치 마치 가치를 잡은 것처럼 사용할 수 있습니다. 이것은 다음과 매우 유사합니다.

const std::vector<int>& rval_ref = return_vector();

내 재 작성에서 분명히 rval_ref비 const 방식으로 사용할 수는 없습니다 .

두 번째 예

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

두 번째 예에서는 런타임 오류를 생성했습니다. rval_ref이제 tmp함수 내부에서 소멸 된 것에 대한 참조를 보유 합니다. 운 좋게도이 코드는 즉시 중단됩니다.

세 번째 예

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

세 번째 예는 첫 번째 예와 거의 같습니다. std::move에가 tmp불필요하며 실제로는 반환 값의 최적화를 억제하므로 성능 pessimization 수있다.

내가하는 일을 코딩하는 가장 좋은 방법은 다음과 같습니다.

모범 사례

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();

즉 C ++ 03에서와 마찬가지로. tmpreturn 문에서 암시 적으로 rvalue로 처리됩니다. 반환 값 최적화 (복사 없음, 이동 없음)를 통해 반환되거나 컴파일러가 RVO를 수행 할 수 없다고 결정하면 vector의 이동 생성자를 사용하여 return을 수행합니다 . RVO가 수행되지 않고 리턴 된 유형에 이동 생성자가없는 경우에만 사본 생성자가 리턴에 사용됩니다.


그들 중 어느 것도 복사하지 않지만 두 번째는 파괴 된 벡터를 가리 킵니다. 명명 된 rvalue 참조는 거의 정규 코드에 존재하지 않습니다. C ++ 03에서 어떻게 사본을 작성했는지를 작성하십시오.

std::vector<int> return_vector()
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();

지금을 제외하고는 벡터가 이동합니다. 사용자 클래스는 대부분의 경우에 그것의를 rvalue 참조를 처리하지 않습니다.


간단한 대답은 정규 참조 코드와 마찬가지로 rvalue 참조 코드를 작성해야하며 정신적으로 99 %의 시간을 동일하게 취급해야한다는 것입니다. 여기에는 참조 반환에 대한 모든 이전 규칙이 포함됩니다 (즉, 로컬 변수에 대한 참조는 절대 반환하지 않습니다).

std :: forward를 활용하고 lvalue 또는 rvalue 참조를 사용하는 일반 함수를 작성할 수있는 템플리트 컨테이너 클래스를 작성하지 않는 한, 이것은 거의 사실입니다.

이동 생성자 및 이동 할당의 가장 큰 장점 중 하나는이를 정의하면 RVO (반환 값 최적화) 및 NRVO (명명 된 반환 값 최적화)가 호출되지 않은 경우 컴파일러에서이를 사용할 수 있다는 것입니다. 컨테이너 및 문자열과 같은 고가의 객체를 메소드에서 효율적으로 값으로 반환하는 데는 상당히 큰 것입니다.

rvalue 참조로 흥미로운 점은 일반적인 함수의 인수로도 사용할 수 있다는 것입니다. 이를 통해 const 참조 (const foo & other) 및 rvalue reference (foo && other) 모두에 대한 과부하가있는 컨테이너를 작성할 수 있습니다. 단순한 생성자 호출로 전달하기에 인수가 다루기 어려울지라도 여전히 수행 할 수 있습니다.

std::vector vec;
for(int x=0; x<10; ++x)
{
    // automatically uses rvalue reference constructor if available
    // because MyCheapType is an unamed temporary variable
    vec.push_back(MyCheapType(0.f));
}


std::vector vec;
for(int x=0; x<10; ++x)
{
    MyExpensiveType temp(1.0, 3.0);
    temp.initSomeOtherFields(malloc(5000));

    // old way, passed via const reference, expensive copy
    vec.push_back(temp);

    // new way, passed via rvalue reference, cheap move
    // just don't use temp again,  not difficult in a loop like this though . . .
    vec.push_back(std::move(temp));
}

STL 컨테이너는 거의 모든 것 (해시 키 및 값, 벡터 삽입 등)에 대한 이동 과부하를 갖도록 업데이트되었으며 가장 많이 볼 수있는 곳입니다.

일반 함수에도 사용할 수 있으며, rvalue 참조 인수 만 제공하면 호출자가 객체를 작성하고 함수가 이동하도록 강제 할 수 있습니다. 이것은 실제로 사용하는 것보다 더 좋은 예이지만 렌더링 라이브러리에서로드 된 모든 리소스에 문자열을 할당하여 디버거에서 각 객체가 나타내는 것을 쉽게 볼 수 있습니다. 인터페이스는 다음과 같습니다.

TextureHandle CreateTexture(int width, int height, ETextureFormat fmt, string&& friendlyName)
{
    std::unique_ptr<TextureObject> tex = D3DCreateTexture(width, height, fmt);
    tex->friendlyName = std::move(friendlyName);
    return tex;
}

그것은 '누수 추상화'의 한 형태이지만 이미 대부분의 시간에 문자열을 만들어야한다는 사실을 이용하고 또 다른 복사를 피할 수 있습니다. 이것은 정확히 고성능 코드는 아니지만 사람들 이이 기능을 사용하지 못할 가능성의 좋은 예입니다. 이 코드는 실제로 변수가 호출의 임시 변수이거나 std :: move 호출 된 변수 여야합니다.

// move from temporary
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));

또는

// explicit move (not going to use the variable 'str' after the create call)
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, std::move(str));

또는

// explicitly make a copy and pass the temporary of the copy down
// since we need to use str again for some reason
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string(str));

그러나 이것은 컴파일되지 않습니다!

string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);

답변 자체 가 아니라 지침입니다. 대부분의 경우 로컬 T&&변수 를 선언하는 데 의미가 없습니다 ( std::vector<int>&& rval_ref). 유형 메소드 std::move()에서 사용하려면 여전히 그들에게 있어야 foo(T&&)합니다. 이미 언급 한 문제 rval_ref는 함수에서 그러한 함수 를 반환하려고 할 때 표준 참조 대 파괴-임시-피아 스코를 얻는다는 것입니다.

대부분 다음과 같은 패턴을 사용합니다.

// Declarations
A a(B&&, C&&);
B b();
C c();

auto ret = a(b(), c());

반환 된 임시 객체에 대한 참조를 보유하지 않으므로 이동 된 객체를 사용하려는 프로그래머의 오류가 발생하지 않습니다.

auto bRet = b();
auto cRet = c();
auto aRet = a(std::move(b), std::move(c));

// Either these just fail (assert/exception), or you won't get 
// your expected results due to their clean state.
bRet.foo();
cRet.bar();

분명히 함수가 실제로 임시 객체가 아닌 객체에 T&&대한 참조 인 객체를 반환하는 경우가 있습니다 (아주 드물지만) .

RVO 관련 : 이러한 메커니즘은 일반적으로 작동하며 컴파일러는 복사를 피할 수 있지만 반환 경로가 분명하지 않은 경우 (예외, if명명 된 객체를 결정하는 조건, 아마도 다른 것들을 결합 할 수 있음) rref는 당신의 구원자입니다 비싼).


이 중 어느 것도 추가 복사를 수행하지 않습니다. RVO를 사용하지 않더라도 새로운 표준에 따르면 수익을 낼 때 이동 구성을 복사하는 것이 좋습니다.

로컬 변수에 대한 참조를 반환하기 때문에 두 번째 예제에서 정의되지 않은 동작이 발생한다고 생각합니다.


첫 번째 답변에 대한 주석에서 이미 언급했듯이 return std::move(...);구성은 지역 변수 반환 이외의 경우에 차이를 만들 수 있습니다. 다음은 멤버 객체 유무에 관계없이 멤버 객체를 반환 할 때 발생하는 상황을 기록하는 실행 가능한 예제입니다 std::move().

#include <iostream>
#include <utility>

struct A {
  A() = default;
  A(const A&) { std::cout << "A copied\n"; }
  A(A&&) { std::cout << "A moved\n"; }
};

class B {
  A a;
 public:
  operator A() const & { std::cout << "B C-value: "; return a; }
  operator A() & { std::cout << "B L-value: "; return a; }
  operator A() && { std::cout << "B R-value: "; return a; }
};

class C {
  A a;
 public:
  operator A() const & { std::cout << "C C-value: "; return std::move(a); }
  operator A() & { std::cout << "C L-value: "; return std::move(a); }
  operator A() && { std::cout << "C R-value: "; return std::move(a); }
};

int main() {
  // Non-constant L-values
  B b;
  C c;
  A{b};    // B L-value: A copied
  A{c};    // C L-value: A moved

  // R-values
  A{B{}};  // B R-value: A copied
  A{C{}};  // C R-value: A moved

  // Constant L-values
  const B bc;
  const C cc;
  A{bc};   // B C-value: A copied
  A{cc};   // C C-value: A copied

  return 0;
}

아마도 return std::move(some_member);특정 클래스 멤버를 실제로 이동하려는 경우에만 의미가 있습니다 (예 : class C의 인스턴스를 작성하기위한 목적으로 수명이 짧은 어댑터 오브젝트를 나타내는 경우) struct A.

객체가 R 값인 경우에도 struct A항상에서 복사 되는 방법에 주목하십시오 . 컴파일러는 의 인스턴스가 더 이상 사용되지 않는다고 말할 방법이 없기 때문 입니다. 에서 , 컴파일러는이 정보 가지고 왜, 도착 이동을 의 인스턴스가하지 않는 상수이다.class Bclass Bclass Bstruct Aclass Cstd::move()struct Aclass C

참고 URL : https://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement



반응형