What does T&& (double ampersand) mean in C++11?

后端 未结 4 776
离开以前
离开以前 2020-11-22 03:55

I\'ve been looking into some of the new features of C++11 and one I\'ve noticed is the double ampersand in declaring variables, like T&& var.

Fo

4条回答
  •  你的背包
    2020-11-22 04:17

    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:

     #include 
     class Sample {
      int *ptr; // large block of memory
      int size;
     public:
      Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
      {
         if (ptr != nullptr) memset(ptr, 0, sz);
      }
      // copy constructor that takes lvalue 
      Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
          nullptr}, size{s.size}
      {
         if (ptr != nullptr) memcpy(ptr, s.ptr, 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
          size = s.size;
    
          if (size != 0) {
            ptr = new int[s.size];
            memcpy(ptr, s.ptr, s.size);
          } else 
             ptr = nullptr;
         }
         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 unique_ptr {
      //...snip
     unique_ptr(unique_ptr&& __u) noexcept; // move constructor
    };
    
     std::unique_ptr ptr2{ptr1};// compile error: no copy ctor.  
    
     // So we must first cast ptr1 to an rvalue 
     std::unique_ptr ptr2{std::move(ptr1)};  
    
    std::unique_ptr TakeOwnershipAndAlter(std::unique_ptr 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 ptr{new int[10]};
    
    // first cast ptr from lvalue to rvalue
    unique_ptr new_owner = TakeOwnershipAndAlter(\
               static_cast&&>(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&&>(ptr) is usually done using std::move

    // first cast ptr from lvalue to rvalue
    unique_ptr 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

提交回复
热议问题