Nice programing

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

nicepro 2020. 9. 29. 18:40
반응형

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


C ++ 11의 새로운 기능 중 일부를 살펴 보았는데 .NET과 같은 변수를 선언 할 때 이중 앰퍼샌드를 발견했습니다 T&& var.

우선,이 짐승은 무엇이라고 불릴까요? Google에서 이와 같은 구두점을 검색 할 수 있기를 바랍니다.

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

언뜻보기에는 이중 참조 (예 : C 스타일 이중 포인터 T** var)로 보이지만 사용 사례를 생각하는 데 어려움을 겪고 있습니다.


rvalue 참조 (표준 제안 문서)를 선언합니다 .

다음은 rvalue 참조에 대한 소개 입니다.

다음은 Microsoft의 표준 라이브러리 개발자 중 한 명이 작성한 rvalue 참조에 대한 환상적이고 심층적 인 내용 입니다 .

주의 : MSDN의 링크 된 문서 ( "Rvalue 참조 : VC10의 C ++ 0x 기능, 2 부")는 Rvalue 참조에 대한 매우 명확한 소개이지만 C ++ 11 초안에서 한때 참이었던 Rvalue 참조에 대한 설명을합니다. 표준이지만 마지막 것은 사실이 아닙니다! 특히, 여러 지점에서 rvalue 참조가 한때 참 이었지만 변경된 lvalue에 바인딩 할 수 있다고 말합니다 (예 : int x; int && rrx = x; 더 이상 GCC에서 컴파일되지 않음) – drewbarbs

C ++ 03 참조 (현재 C ++ 11에서 lvalue 참조라고 함)의 가장 큰 차이점은 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에서는 이동 생성자를 오버로드 할 수 있습니다.

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

여기서 큰 차이점에 주목하십시오. 이동 생성자는 실제로 인수를 수정합니다. 이렇게하면 임시를 구성중인 개체로 효과적으로 "이동"하여 불필요한 복사본을 제거합니다.

이동 생성자는 임시 및 std::move함수를 사용하여 rvalue 참조로 명시 적으로 변환되는 상수가 아닌 lvalue 참조에 사용 됩니다 (변환 만 수행함). 다음 코드는 f1및에 대한 이동 생성자를 호출합니다 f2.

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&하더라도 리터럴 5에 바인딩되지 않습니다 . 글쎄, 우리는 대신 사용할 수 있지만, const가 아닌 참조로 생성자 인수를 취하면 어떻게 될까요? 진정으로 일반적인 팩토리 함수를 만들려면 팩토리를 켜고 켜야 합니다 . 팩토리가 1 개의 매개 변수 유형을 취하면 괜찮을 수 있지만 각 추가 매개 변수 유형은 필요한 과부하 세트에 2를 곱합니다. 이는 매우 빠르게 유지 보수 할 수 없습니다.foointA1 const&fooA1&A1 const&

rvalue 참조는 표준 라이브러리가 std::forwardlvalue / rvalue 참조를 적절하게 전달할 수 있는 함수 를 정의하도록 허용함으로써이 문제를 해결합니다 . 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로 호출되면 T의 생성자가 rvalue로 호출됩니다. 팩토리가 lvalue로 호출되면 T의 생성자는 lvalue로 호출됩니다. 개선 된 공장 기능은 다음과 같은 특별한 규칙 때문에 작동합니다.

함수 파라미터 입력 양식의 경우 템플릿 파라미터 및 함수 인자는 타입의 좌변 인 , 유형 템플릿 인자 추론에 사용된다.T&&TAA&

따라서 공장을 다음과 같이 사용할 수 있습니다.

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

중요한 rvalue 참조 속성 :

  • 과부하 해결을 위해 lvalues는 lvalue 참조에 대한 바인딩을 선호하고 rvalue는 rvalue 참조에 대한 바인딩을 선호합니다 . 따라서 임시 사용자가 복사 생성자 / 할당 연산자보다 이동 생성자 / 이동 할당 연산자를 호출하는 것을 선호하는 이유입니다.
  • rvalue 참조는 암시 적으로 rvalue와 암시 적 변환의 결과 인 임시에 바인딩됩니다 . 즉, float f = 0f; int&& i = f;float는 암시 적으로 int로 변환 될 수 있기 때문에 잘 형성됩니다. 참조는 변환의 결과 인 임시에 대한 것입니다.
  • 명명 된 rvalue 참조는 lvalue입니다. 명명되지 않은 rvalue 참조는 rvalue입니다. 다음에서 std::move호출이 필요한 이유를 이해하는 것이 중요합니다 .foo&& r = foo(); foo f = std::move(r);

rvalue 참조를 나타냅니다. Rvalue 참조는 명시 적으로 달리 생성되지 않는 한 임시 개체에만 바인딩됩니다. 특정 상황에서 객체를 훨씬 더 효율적으로 만들고 템플릿 코드를 크게 단순화하는 완벽한 전달이라는 기능을 제공하는 데 사용됩니다.

C ++ 03에서는 변경 불가능한 lvalue의 복사본과 rvalue를 구별 할 수 없습니다.

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);

In C++0x, this is not the case.

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);

Consider the implementation behind these constructors. In the first case, the string has to perform a copy to retain value semantics, which involves a new heap allocation. However, in the second case, we know in advance that the object which was passed in to our constructor is immediately due for destruction, and it doesn't have to remain untouched. We can effectively just swap the internal pointers and not perform any copying at all in this scenario, which is substantially more efficient. Move semantics benefit any class which has expensive or prohibited copying of internally referenced resources. Consider the case of std::unique_ptr- now that our class can distinguish between temporaries and non-temporaries, we can make the move semantics work correctly so that the unique_ptr cannot be copied but can be moved, which means that std::unique_ptr can be legally stored in Standard containers, sorted, etc, whereas C++03's std::auto_ptr cannot.

Now we consider the other use of rvalue references- perfect forwarding. Consider the question of binding a reference to a reference.

std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template

Can't recall what C++03 says about this, but in C++0x, the resultant type when dealing with rvalue references is critical. An rvalue reference to a type T, where T is a reference type, becomes a reference of type T.

(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&

Consider the simplest template function- min and max. In C++03 you have to overload for all four combinations of const and non-const manually. In C++0x it's just one overload. Combined with variadic templates, this enables perfect forwarding.

template<typename A, typename B> auto min(A&& aref, B&& bref) {
    // for example, if you pass a const std::string& as first argument,
    // then A becomes const std::string& and by extension, aref becomes
    // const std::string&, completely maintaining it's type information.
    if (std::forward<A>(aref) < std::forward<B>(bref))
        return std::forward<A>(aref);
    else
        return std::forward<B>(bref);
}

I left off the return type deduction, because I can't recall how it's done offhand, but that min can accept any combination of lvalues, rvalues, const lvalues.


The term for T&& when used with type deduction (such as for perfect forwarding) is known colloquially as a forwarding reference. The term "universal reference" was coined by Scott Meyers in this article, but was later changed.

That is because it may be either r-value or l-value.

Examples are:

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

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

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

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

More discussion can be found in the answer for: Syntax for universal references


An rvalue reference is a type that behaves much like the ordinary reference X&, with several exceptions. The most important one is that when it comes to function overload resolution, lvalues prefer old-style lvalue references, whereas rvalues prefer the new rvalue references:

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&&)

So what is an rvalue? Anything that is not an lvalue. An lvalue being an expression that refers to a memory location and allows us to take the address of that memory location via the & operator.

It is almost easier to understand first what rvalues accomplish with an example:

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

The constructor and assignment operators have been overloaded with versions that take rvalue references. Rvalue references allow a function to branch at compile time (via overload resolution) on the condition "Am I being called on an lvalue or an rvalue?". This allowed us to create more efficient constructor and assignment operators above that move resources rather copy them.

The compiler automatically branches at compile time (depending on the whether it is being invoked for an lvalue or an rvalue) choosing whether the move constructor or move assignment operator should be called.

Summing up: rvalue references allow move semantics (and perfect forwarding, discussed in the article link below).

One practical easy-to-understand example is the class template std::unique_ptr. Since a unique_ptr maintains exclusive ownership of its underlying raw pointer, unique_ptr's can't be copied. That would violate their invariant of exclusive ownership. So they do not have copy constructors. But they do have move constructors:

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) is usually done using std::move

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

An excellent article explaining all this and more (like how rvalues allow perfect forwarding and what that means) with lots of good examples is Thomas Becker's C++ Rvalue References Explained. This post relied heavily on his article.

A shorter introduction is A Brief Introduction to Rvalue References by Stroutrup, et. al

참고URL : https://stackoverflow.com/questions/5481539/what-does-t-double-ampersand-mean-in-c11

반응형