Что означает T&& (double ampersand) в C++11?

Я изучал некоторые из новых функций C++11, и я заметил, что двойной амперсанд в объявлении переменных, таких как T&& var.

для начала, как называется этот зверь? Я хотел Google позволит искать препинания такой.

что это в смысле?

на первый взгляд, это кажется двойной ссылкой (например, двойные указатели C-style T** var), но мне трудно думать об использовании на этот случай.

4 ответов


он объявляет rvalue-ссылке (стандарт предложение doc).

вот введение в rvalue ссылки.

вот фантастический углубленный взгляд на ссылки rvalue одной из стандартных библиотек Microsoft разработчики. (Но см. предупреждение в комментариях после этого ответа, прежде чем читать эту статью.)

самая большая разница между ссылкой на C++03 (теперь называется lvalue ссылка в C++11) заключается в том, что он может привязываться к rvalue как временный, без необходимости быть const. Таким образом, этот синтаксис теперь легален:

T&& r = T();

ссылки rvalue в первую очередь предусматривает следующее:

переместить семантика. Теперь можно определить конструктор перемещения и оператор назначения перемещения, который принимает ссылку rvalue вместо обычной ссылки const-lvalue. Перемещение функционирует как копия, за исключением того, что не обязано сохранять источник неизменным; фактически, он обычно изменяет источник таким образом, что он больше не владеет перемещенными ресурсами. Это отлично подходит для устранения посторонних копий, особенно в стандартных реализациях библиотек.

например, конструктор копирования может выглядеть так:

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

обратите внимание на большую разницу здесь: Конструктор move фактически изменяет свой аргумент. Это позволит эффективно "переместить" временное в строящийся объект, тем самым исключив ненужную копию.

конструктор move будет использоваться для временных и для ссылок non-const lvalue, которые явно преобразуется в ссылки rvalue с помощью std::move функция (она просто выполняет преобразование). Следующий код вызывает конструктор move для 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, Даже если fooконструктор принимает int. Ну, мы могли бы вместо этого использовать A1 const&, а если foo принимает аргумент конструктора по ссылке non-const? Для того чтобы сделать поистине родовую функцию фабрики, мы должны были бы перегрузить фабрику дальше A1& и A1 const&. Это может быть хорошо, если фабрика принимает 1 тип параметра, но каждый дополнительный тип параметра умножит необходимую перегрузку на 2. Это очень быстро unmaintainable.

исправление ссылок rvalue эта проблема, позволяя стандартной библиотеке определить std::forward функция, которая может правильно пересылать ссылки lvalue/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'конструктор С. Это означает, что если factory вызывается с rvalue,Tс конструктором вызывается с rvalue. Если фабрика вызывается с lvalue,вызывается с помощью lvalue. Улучшенная функция фабрики работает из - за одного специального правила:

когда тип параметра функции форма T&& здесь T - это шаблон параметр и аргумент функции является lvalue типа A, типа A& это используется для вывода аргумента шаблона.

таким образом, мы можем использовать завод как Итак:

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

важные свойства ссылки rvalue:

  • для разрешения перегрузки, значения lvalue предпочитают привязки к ссылки lvalue и rvalue предпочитают привязки к rvalue-ссылки. Следовательно, почему временные предпочитают вызывать конструктор перемещения / оператор назначения перемещения над конструктором копирования / оператором назначения.
  • ссылки rvalue будут неявно привязываться к rvalues и к временным результат неявного преобразования. т. е. float f = 0f; int&& i = f; хорошо сформирован, потому что float неявно конвертируется в int; ссылка будет временной, которая является результатом преобразования.
  • именованные ссылки rvalue являются lvalues. Неназванные ссылки rvalue являются rvalues. это важно, чтобы понять, почему std::move вызов необходим в:foo&& r = foo(); foo f = std::move(r);

он обозначает ссылку rvalue. Ссылки Rvalue будут привязываться только к временным объектам, если явно не создано иначе. Они используются, чтобы сделать объекты гораздо более эффективными при определенных обстоятельствах и обеспечить средство, известное как perfect forwarding, что значительно упрощает код шаблона.

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

в C++0x это не случай.

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

рассмотрим реализацию этих конструкторов. В первом случае строка должна выполнить копию для сохранения семантики значения, которая включает новое распределение кучи. Однако, во втором случае, мы заранее знаем, что объект, который был передан в конструктор сразу за разрушение, и он не должен оставаться нетронутым. Мы можем эффективно просто поменять внутренние указатели и не выполнять копирование вообще в этом сценарии, который существенно эффективнее. Семантика перемещения полезна любому классу, который имеет дорогостоящее или запрещенное копирование внутренних ресурсов. Рассмотрим случай std::unique_ptr- теперь, когда наш класс может различать временные и не-временные, мы можем заставить семантику перемещения работать правильно, чтобы unique_ptr не может быть скопирован, но может быть перемещен, что означает, что std::unique_ptr можно законно хранить в стандартных контейнерах, отсортированы, и т. д., Тогда как C++03 это std::auto_ptr не может.

теперь мы рассмотрим другое использование ссылок rvalue-perfect forwarding. Рассмотрим вопрос о привязке ссылки к ссылке.

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

не могу вспомнить, что говорит об этом C++03, но в C++0x результирующий тип при работе со ссылками rvalue имеет решающее значение. Ссылка rvalue на тип T, где T-ссылочный тип, становится ссылкой типа 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&&

рассмотрим простейшую функцию шаблона-min и max. В C++03 вы должны перегрузить все четыре комбинации const и non-const вручную. В C++0x это всего лишь одна перегрузка. Совмещенный с variadic шаблонами, это включает совершенное препровождение.

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

Я оставил вычет типа возврата, потому что я не могу вспомнить, как это делается навскидку, но этот min может принимать любую комбинацию lvalues, rvalues, const lvalues.


на срок T&& при использовании с типом вычета (например, для идеальной пересылки) известен в просторечии как ссылка для пересылки. Термин "универсальная ссылка" был придуман Скоттом Мейерсом в этой статье, но позже был изменен.

это потому, что это может быть либо r-значение, либо l-значение.

пример:

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

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

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

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

больше обсуждения можно найти в ответе для:синтаксис для универсального ссылки


ссылка 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 поддерживает исключительное владение своим базовым необработанным указателем, 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 позволяют идеальную пересылку и что это значит) с большим количеством хороших примеров Томас Беккер C++ Rvalue Ссылки Объясняются. Этот пост во многом опирался на его статью.

более короткое введение краткое введение в ссылки Rvalue by Stroutrup, et. Эл!--6-->