Что такое «rvalue reference для * this»?

Вышло через предложение, называемое «ссылкой rvalue для * this» на странице состояния C ++ 11 clang.

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

Ссылка на документ заявки на странице: N2439 (Расширение семантики перемещения до * этого), но я также не получаю много примеров оттуда.

О чем эта особенность?

Во-первых, «ref-qualifiers for * this» является просто «маркетинговым заявлением». Тип *this никогда не изменяется, см. Нижнюю часть этого сообщения. Однако легче понять это с помощью этой формулировки.

Затем следующий код выбирает функцию, которая должна быть вызвана на основе ref-qualifier «неявного параметра объекта» функции :

 // t.cpp #include  struct test{ void f() &{ std::cout << "lvalue object\n"; } void f() &&{ std::cout << "rvalue object\n"; } }; int main(){ test t; tf(); // lvalue test().f(); // rvalue } 

Вывод:

 $ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp $ ./a.out lvalue object rvalue object 

Все это сделано для того, чтобы вы могли воспользоваться тем фактом, когда объект, на который вызывается функция, является rvalue (например, неназванным временным). В качестве следующего примера возьмем следующий код:

 struct test2{ std::unique_ptr heavy_resource; test2() : heavy_resource(new int[500]) {} operator std::unique_ptr() const&{ // lvalue object, deep copy std::unique_ptr p(new int[500]); for(int i=0; i < 500; ++i) p[i] = heavy_resource[i]; return p; } operator std::unique_ptr() &&{ // rvalue object // we are garbage anyways, just move resource return std::move(heavy_resource); } }; 

Это может быть немного надуманным, но вы должны получить эту идею.

Обратите внимание, что вы можете комбинировать cv-квалификаторы ( const и volatile ) и ref-qualifiers ( & и && ).


Примечание. Многие стандартные цитаты и описание разрешения перегрузки после здесь!

† Чтобы понять, как это работает, и почему ответ @Nicol Bolas, по крайней мере, частично ошибочен, нам нужно немного копаться в стандарте C ++ (часть, объясняющая, почему @ Ответ Никола неверен, внизу, если вы только заинтересован в этом).

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

Во-первых, важно посмотреть, как работает разрешение перегрузки для функций-членов:

§13.3.1 [over.match.funcs]

p2 Набор функций-кандидатов может содержать как функции-члены, так и не-члены, которые должны быть разрешены в отношении одного и того же списка аргументов. Таким образом, список аргументов и параметров сопоставим в этом гетерогенном наборе, считается , что функция-член имеет дополнительный параметр, называемый неявным параметром объекта, который представляет объект, для которого была вызвана функция-член . [...]

p3 Аналогичным образом контекст может, при необходимости, создавать список аргументов, содержащий подразумеваемый аргумент объекта для обозначения объекта, который будет использоваться.

Почему нам даже нужно сравнивать функции членов и нечленов? Перегрузка оператора, вот почему. Учти это:

 struct foo{ foo& operator<<(void*); // implementation unimportant }; foo& operator<<(foo&, char const*); // implementation unimportant 

Вы, конечно, хотите, чтобы следующее вызывало свободную функцию, не так ли?

 char const* s = "free foo!\n"; foo f; f << s; 

Вот почему функции-члены и не-члены включены в так называемый набор перегрузки. Чтобы сделать разрешение менее сложным, существует полужирная часть стандартной цитаты. Кроме того, это важный бит для нас (тот же самый пункт):

p4 Для нестатических функций-членов тип параметра неявного объекта

  • «Lvalue reference to cv X » для функций, объявленных без ref-определителя или с & ref-qualifier

  • «Rvalue reference to cv X » для функций, объявленных с помощью && ref-qualifier

где X - class, членом которого является член, а cv - cv-квалификация в объявлении функции-члена. [...]

p5 Во время разрешения перегрузки [...] [t] он неявный параметр объекта [...] сохраняет свою идентификацию, поскольку преобразования в соответствующем аргументе должны подчиняться этим дополнительным правилам:

  • временный объект не может быть введен для хранения аргумента для неявного параметра объекта; а также

  • никакие пользовательские преобразования не могут применяться для достижения соответствия типа с ним

[...]

(Последний бит просто означает, что вы не можете обмануть разрешение перегрузки на основе неявных преобразований объекта, на который вызывается функция-член (или оператор).)

Давайте рассмотрим первый пример в верхней части этого сообщения. После вышеупомянутого преобразования набор перегрузки выглядит примерно так:

 void f1(test&); // will only match lvalues, linked to 'void test::f() &' void f2(test&&); // will only match rvalues, linked to 'void test::f() &&' 

Затем список аргументов, содержащий подразумеваемый аргумент объекта , сопоставляется с параметром-списком каждой функции, содержащейся в наборе перегрузки. В нашем случае список аргументов будет содержать только этот аргумент объекта. Посмотрим, как это выглядит:

 // first call to 'f' in 'main' test t; f1(t); // 't' (lvalue) can match 'test&' (lvalue reference) // kept in overload-set f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference) // taken out of overload-set 

Если после проверки всех перегрузок в наборе остается только один, разрешение перегрузки сработало и вызывается функция, связанная с этой преобразованной перегрузкой. То же самое касается второго вызова «f»:

 // second call to 'f' in 'main' f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference) // taken out of overload-set f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference) // kept in overload-set 

Обратите внимание, однако, что если бы мы не предоставили какой - либо ref-квалификатор (и, как таковой, не перегружали функцию), то f1 будет соответствовать rvalue (все еще §13.3.1 ):

p5 [...] Для нестатических функций-членов, объявленных без ref-квалификатора , применяется дополнительное правило:

  • даже если параметр неявного объекта не является const -qualified, значение rvalue может быть привязано к параметру, если во всех других отношениях аргумент может быть преобразован в тип параметра неявного объекта.
 struct test{ void f() { std::cout << "lvalue or rvalue object\n"; } }; int main(){ test t; tf(); // OK test().f(); // OK too } 

Теперь о том, почему @ Николь ответил, по крайней мере, частично неправильно. Он говорит:

Обратите внимание, что это объявление изменяет тип *this .

Это неправильно, *this всегда значение lvalue:

§5.3.1 [expr.unary.op] p1

Оператор унарного * выполняет косвенное обращение: выражение, к которому оно применяется, должно быть указателем на тип объекта или указателем на тип функции, а результатом является lvalue, относящееся к объекту или функции, к которой относится выражение.

§9.3.2 [class.this] p1

В теле нестатической (9.3) функции-члена ключевым словом является выражение prvalue, значение которого является адресом объекта, для которого вызывается функция. Тип this в членной функции classа X есть X* . [...]

Существует дополнительный прецедент для формы ref-квалификатора lvalue. C ++ 98 имеет язык, позволяющий вызывать функции non const для экземпляров classа, которые являются rvalues. Это приводит ко всем видам странности, которые противоречат самой концепции rvalueness и отклоняются от того, как работают встроенные типы:

 struct S { S& operator ++(); S* operator &(); }; S() = S(); // rvalue as a left-hand-side of assignment! S& foo = ++S(); // oops, dangling reference &S(); // taking address of rvalue... 

Реффикаторы Lvalue решают следующие проблемы:

 struct S { S& operator ++() &; S* operator &() &; const S& operator =(const S&) &; }; 

Теперь операторы работают так же, как и у встроенных типов, принимая только lvalues.

Допустим, у вас есть две функции для classа, имеющие одинаковое имя и подпись. Но один из них объявлен const :

 void SomeFunc() const; void SomeFunc(); 

Если экземпляр classа не const , разрешение перегрузки будет предпочтительно выбирать неконстантную версию. Если экземпляр const , пользователь может вызвать только версию const . И this указатель является указателем const , поэтому экземпляр не может быть изменен.

Что «ссылка r-value для этого» позволяет вам добавить еще одну альтернативу:

 void RValueFunc() &&; 

Это позволяет вам иметь функцию, которую можно вызвать только в том случае, если пользователь называет ее правильным значением r. Так что если это в типе Object :

 Object foo; foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value. Object().RValueFunc(); //calls the non-const, && version. 

Таким образом, вы можете специализировать поведение, основанное на доступе к объекту через r-значение или нет.

Обратите внимание, что вам не разрешается перегружать между справочными версиями r-value и версиями без ссылки. То есть, если у вас есть имя функции-члена, все его версии либо используют квалификаторы l / r-value, либо ни один из них не выполняет. Вы не можете этого сделать:

 void SomeFunc(); void SomeFunc() &&; 

Вы должны сделать это:

 void SomeFunc() &; void SomeFunc() &&; 

Обратите внимание, что это объявление изменяет тип *this . Это означает, что в && версии все члены доступа используются как ссылки r-значения. Таким образом, становится возможным легко перемещаться из объекта. Пример, приведенный в первой версии предложения (примечание: следующее может быть неверным с окончательной версией C ++ 11, оно прямо из первоначального предложения «r-value from this»):

 class X { std::vector data_; public: // ... std::vector const & data() const & { return data_; } std::vector && data() && { return data_; } }; X f(); // ... X x; std::vector a = x.data(); // copy std::vector b = f().data(); // move 
Давайте будем гением компьютера.