c++ - type - rvalue reference lvalue




C++ 11에서 T &&(이중 앰퍼샌드)는 무엇을 의미합니까? (3)

rvalue 참조는 여러 가지 예외를 제외하고는 일반 참조 X &와 매우 유사한 유형입니다. 가장 중요한 점은 과부하 기능을 수행 할 때 lvalues는 구식 lvalue 참조를 선호하지만 rvalues는 새로운 rvalue 참조를 선호한다는 것입니다.

void foo(X& x);  // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar();

foo(x);        // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

그래서 rvalue 무엇입니까? lvalue가 아닌 것은 무엇이든. lvalue는 메모리 위치를 참조하고 & 연산자를 통해 해당 메모리 위치의 주소를 가져올 수있는 표현식입니다.

예를 통해 rvalues가 달성하는 것을 먼저 이해하는 것이 거의 쉽습니다.

 class Sample {
  int *ptr; // large block of memory
  int size;
 public:
  Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
  {}
  // copy constructor that takes lvalue 
  Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
      nullptr}, size{s.size}
  {
     std::cout << "copy constructor called on lvalue\n";
  }

  // move constructor that take rvalue
  Sample(Sample&& s) 
  {  // steal s's resources
     ptr = s.ptr;
     size = s.size;        
     s.ptr = nullptr; // destructive write
     s.size = 0;
     cout << "Move constructor called on rvalue." << std::endl;
  }    
  // normal copy assignment operator taking lvalue
  Sample& operator=(const Sample& s)
  {
   if(this != &s) {
      delete [] ptr; // free current pointer
      ptr = new int[s.size]; 
      size = s.size; 
    }
    cout << "Copy Assignment called on lvalue." << std::endl;
    return *this;
  }    
 // overloaded move assignment operator taking rvalue
 Sample& operator=(Sample&& lhs)
 {
   if(this != &s) {
      delete [] ptr; //don't let ptr be orphaned 
      ptr = lhs.ptr;   //but now "steal" lhs, don't clone it.
      size = lhs.size; 
      lhs.ptr = nullptr; // lhs's new "stolen" state
      lhs.size = 0;
   }
   cout << "Move Assignment called on rvalue" << std::endl;
   return *this;
 }
//...snip
};     

생성자 및 대입 연산자에는 rvalue 참조를 사용하는 버전이 오버로드되었습니다. Rvalue 참조는 함수가 "lvalue 또는 rvalue에서 호출되고 있습니까?"라는 조건에서 함수가 컴파일 타임에 (과부하 해결을 통해) 분기하도록 허용합니다. 이것은 우리가 리소스를 옮기는 대신 위에보다 효율적인 생성자와 대입 연산자를 생성 할 수있게 해줍니다.

컴파일러는 이동 생성자 또는 이동 대입 연산자를 호출할지 여부를 선택하여 컴파일 타임에 (lvalue 또는 rvalue에 대해 호출되는지 여부에 따라) 자동으로 분기합니다.

요약 : rvalue 참조는 이동 의미 (및 완벽한 전달, 아래 기사 링크에서 설명)를 허용합니다.

실제 이해하기 쉬운 예제 중 하나는 클래스 템플릿 std :: unique_ptr 입니다. unique_ptr는 기본 raw 포인터의 배타적 소유권을 유지하므로 unique_ptr을 복사 할 수 없습니다. 그것은 독점권의 불변성에 위배됩니다. 그래서 그들은 복사 생성자를 가지고 있지 않습니다. 그러나 그들은 이동 생성자를 가지고 있습니다.

template<class T> class unique_ptr {
  //...snip
 unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};

 std::unique_ptr<int[] pt1{new int[10]};  
 std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.  

 // So we must first cast ptr1 to an rvalue 
 std::unique_ptr<int[]> ptr2{std::move(ptr1)};  

std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
 int size)      
{
  for (auto i = 0; i < size; ++i) {
     param[i] += 10;
  }
  return param; // implicitly calls unique_ptr(unique_ptr&&)
}

// Now use function     
unique_ptr<int[]> ptr{new int[10]};

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
           static_cast<unique_ptr<int[]>&&>(ptr), 10);

cout << "output:\n";

for(auto i = 0; i< 10; ++i) {
   cout << new_owner[i] << ", ";
}

output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 

static_cast<unique_ptr<int[]>&&>(ptr) 은 보통 std :: move를 사용하여 수행됩니다

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);

우수 사례가 많이있는이 모든 것을 설명하는 훌륭한 기사 (rvalues가 완벽한 전달을 허용하고 그 의미가 무엇인지와 같은)는 Thomas Becker의 C ++ Rvalue References Explained 입니다. 이 게시물은 그의 기사에 크게 의존했습니다.

더 짧은 소개는 Stroutrup 등의 referencesreferences 입니다. 알

C ++ 11의 새로운 기능 중 일부를 살펴 보았습니다. T&& var 와 같은 변수 선언에 이중 앰퍼샌드가 있습니다.

시작을 위해이 짐승은 무엇이라고 불 립니까? Google이 이와 같이 구두점을 검색 할 수 있기를 바랍니다.

그것은 정확히 무엇을 의미합니까?

언뜻보기에는 (C 스타일의 이중 포인터 T** var 와 같은) 이중 참조 인 것으로 보이지만 그 경우를 생각하면 어렵습니다.


그것은 rvalue reference (표준 제안 문서)를 선언합니다.

다음은 rvalue references 대한 소개입니다.

Microsoft의 표준 라이브러리 developers 중 하나 인 rvalue reference에 대한 심층적 인 살펴 봅니다. (그러나이 기사를 읽기 전에이 대답의 주석에 나오는주의를 참조하십시오.)

C ++ 03 참조 (현재 C ++ 11에서 좌변 참조라고 함)의 가장 큰 차이점은 const 일 필요없이 임시와 같은 rvalue에 바인딩 할 수 있다는 것입니다. 따라서이 구문은 이제 합법적입니다.

T&& r = T();

rvalue 참조는 주로 다음을 제공합니다.

의미 이동 . 이제 일반 const-lvalue 참조 대신 rvalue 참조를 사용하는 이동 생성자 및 이동 대입 연산자를 정의 할 수 있습니다. 이동은 소스와 동일하게 유지해야하는 경우를 제외하고 사본과 유사하게 작동합니다. 사실, 소스가 더 이상 이동 된 자원을 소유하지 않도록 소스를 수정합니다. 이는 특히 표준 라이브러리 구현에서 외부 복사본을 제거하는 데 유용합니다.

예를 들어 복사 생성자는 다음과 같습니다.

foo(foo const& other)
{
    this->length = other.length;
    this->ptr = new int[other.length];
    copy(other.ptr, other.ptr + other.length, this->ptr);
}

이 생성자가 임시로 전달 된 경우 임시가 손상된다는 것을 알기 때문에 복사본이 필요하지 않습니다. 일시적으로 할당 된 자원을 사용하지 않는 이유는 무엇입니까? C ++ 03에는 일시적으로 통과 한 것으로 판단 할 수 없으므로 복사본을 방지 할 수있는 방법이 없습니다. C ++ 11에서는 move 생성자를 오버로드 할 수 있습니다.

foo(foo&& other)
{
   this->length = other.length;
   this->ptr = other.ptr;
   other.length = 0;
   other.ptr = nullptr;
}

여기서 큰 차이가 있음을 주목하십시오. 이동 생성자는 실제로 인수를 수정합니다. 이렇게하면 임시 객체를 효과적으로 생성 된 객체로 "이동"하여 불필요한 사본을 제거 할 수 있습니다.

이동 생성자는 임시 및 std::move 함수를 사용하여 rvalue 참조로 명시 적으로 변환되는 비 const 왼쪽 값 참조에 사용됩니다 (변환 만 수행함). 다음 코드는 f1f2 의 이동 생성자를 호출합니다.

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

완벽한 전달 . rvalue 참조를 사용하면 템플릿 함수에 대한 인수를 올바르게 전달할 수 있습니다. 예를 들어이 팩토리 함수를 보자.

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
    return std::unique_ptr<T>(new T(a1));
}

factory<foo>(5) 호출하면 인수는 int& 인 것으로 추론되며, 이는 foo 의 생성자가 int 취하는 경우에도 리터럴 5에 바인딩되지 않습니다. 음, 우리는 대신 A1 const& 를 사용할 수 있습니다. 그러나 foo 가 비 const 참조로 생성자 인수를받는다면 어떨까요? 진정한 제네릭 팩토리 함수를 만들기 위해서 우리는 A1&A1 const& 에 팩토리를 오버로드시켜야 할 것입니다. 팩토리가 1 개의 매개 변수 유형을 취하는 경우 괜찮을 수 있지만 추가 매개 변수 유형마다 2로 설정된 필요한 오버로드가 곱해질 것입니다. 이는 매우 신속하게 유지할 수 없습니다.

rvalue 참조는 표준 라이브러리가 lvalue / rvalue 참조를 제대로 전달할 수있는 std::forward 함수를 정의 할 수있게하여이 문제를 해결합니다. std::forward 가 어떻게 작동하는지에 대한 더 자세한 정보는 이 훌륭한 대답을보십시오 .

이렇게하면 다음과 같이 팩토리 함수를 정의 할 수 있습니다.

template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
    return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}

이제 인수의 rvalue / lvalue-ness는 T 의 생성자에 전달 될 때 보존됩니다. 즉, rvalue를 사용하여 factory를 호출하면 T 의 생성자가 rvalue와 함께 호출됩니다. lvalue로 팩토리가 호출되면 T 의 생성자가 lvalue와 함께 호출됩니다. 향상된 팩토리 함수는 다음과 같은 특별한 규칙 때문에 작동합니다.

함수 매개 변수 형식이 T&& 형식 인 경우 T 는 템플릿 매개 변수이고 함수 인수는 형식 A 의 lvalue이고 형식 A& 는 템플릿 인수 차감에 사용됩니다.

따라서 다음과 같이 factory를 사용할 수 있습니다.

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

중요한 rvalue 참조 속성 :

  • 과부하 해결의 경우 lvalue는 lvalue 참조에 바인딩을 선호하고 rvalues는 rvalue 참조에 바인딩하는 것을 선호합니다 . 따라서 왜 임시 연산자가 복사 생성자 / 대입 연산자를 통해 이동 생성자 / 이동 대입 연산자를 호출하는지 더 선호합니다.
  • rvalue 참조는 암시 적 변환의 결과 인 rvalues ​​및 temporaries에 암시 적으로 바인딩됩니다 . ie float f = 0f; int&& i = f; float f = 0f; int&& i = f; float가 암시 적으로 int로 변환 될 수 있으므로 잘 형성됩니다. 그 참조는 변환의 결과 인 일시적 일 것입니다.
  • 명명 된 rvalue 참조는 lvalues입니다. 이름없는 값은 rvalues입니다. 이것은 std::move 호출이 필요한 이유를 이해하는 데 중요합니다. foo&& r = foo(); foo f = std::move(r); foo&& r = foo(); foo f = std::move(r);

유형 공제 (예 : 완벽한 전달)와 함께 사용될 때 T&& 라는 용어는 구두점으로 전달 참조 라고 알려져 있습니다. "범용 참조"라는 용어 는이 기사에서 Scott Meyers 만들었지 만 나중에 변경되었습니다.

그것은 r 값이거나 l 값일 수 있기 때문입니다.

예 :

// template
template<class T> foo(T&& t) { ... }

// auto
auto&& t = ...;

// typedef
typedef ... T;
T&& t = ...;

// decltype
decltype(...)&& t = ...;

더 많은 논의는 다음에 대한 답에서 찾을 수 있습니다 : Universal references에 대한 구문





perfect-forwarding