Что означает T && (двойной амперсанд) в C ++ 11? - PullRequest
712 голосов
/ 30 марта 2011

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

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

Что конкретно это означает ?

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

Ответы [ 4 ]

616 голосов
/ 30 марта 2011

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

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

Вот фантастический всесторонний взгляд на ссылки на значения одного из стандартных библиотек Microsoft разработчиков .

ВНИМАНИЕ: связанная статья на MSDN («Ссылки Rvalue: особенности C ++ 0x в VC10, часть 2») является очень четким введением в ссылки на Rvalue, но дает утверждения о Rvalue ссылки, которые когда-то были верны в проекте стандарта C ++ 11, но не верны для окончательного варианта! В частности, в различных точках говорится, что ссылки на rvalue могут связываться с lvalue, что когда-то было истинно, но было изменено (например, int x; int && rrx = x; больше не компилируется в GCC) - drewbarbs 13 июля '14 в 16:12

Самым большим отличием ссылки на 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;
}

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

Конструктор перемещения будет использоваться для временных и неконстантных ссылок на lvalue, которые явно преобразуются в ссылки на rvalue с помощью функции std::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 принимает аргумент конструктора по неконстантной ссылке? Чтобы создать действительно универсальную фабричную функцию, нам пришлось бы перегружать фабрику на A1& и A1 const&. Это может быть хорошо, если фабрика принимает 1 тип параметра, но каждый дополнительный тип параметра умножает необходимую перегрузку, установленную на 2. Это очень быстро не поддерживается.

Ссылки на 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. Если factory вызывается с lvalue, конструктор T вызывается с lvalue. Улучшенная функция фабрики работает благодаря одному специальному правилу:

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

Таким образом,мы можем использовать фабрику следующим образом:

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

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

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

Обозначает ссылку на значение. Ссылки 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&);

В 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 - идеальная пересылка. Рассмотрим вопрос привязки ссылки к ссылке.

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 это всего лишь одна перегрузка. В сочетании с шаблонами с переменными параметрами это обеспечивает идеальную пересылку.

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 может принимать любую комбинацию lvalue, rvalues, const lvalues.

23 голосов
/ 17 января 2013

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

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

Примеры:

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

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

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

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

Дополнительную информацию можно найти в ответе на: Синтаксис для универсальных ссылок

11 голосов
/ 03 ноября 2016

Ссылка 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);

Отличная статья, объясняющая все это и многое другое(например, как значения rvalue позволяют совершенную пересылку и что это значит) со множеством хороших примеров объясняется C ++ Rvalue References Томаса Беккера .Этот пост в значительной степени опирался на его статью.

Более короткое введение - Краткое введение в Rvalue Список литературы от Stroutrup, et.аль

...