Во-первых, «ref-qualifiers for * this» - это просто «маркетинговое заявление». Тип *this
никогда не меняется, смотрите в нижней части этого поста. Это намного проще понять с помощью этой формулировки.
Затем следующий код выбирает функцию для вызова на основе ref-квалификатора «параметра неявного объекта» функции † :
// t.cpp
#include <iostream>
struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};
int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}
Вывод:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Все это сделано для того, чтобы вы могли использовать тот факт, что объект, для которого вызывается функция, является значением r (например, безымянный временный объект). Возьмем следующий код в качестве дополнительного примера:
struct test2{
std::unique_ptr<int[]> heavy_resource;
test2()
: heavy_resource(new int[500]) {}
operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];
return p;
}
operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};
Это может быть немного надуманным, но вы должны понять.
Обратите внимание, что вы можете комбинировать cv-квалификаторы ( const
и volatile
) и ref-квалификаторы ( &
и &&
).
Примечание: многие стандартные кавычки и объяснение разрешения перегрузки приведены здесь!
† Чтобы понять, как это работает, и почему ответ @Nicol Bolas хотя бы частично неверен, нам нужно немного покопаться в стандарте C ++ (часть, объясняющая, почему ответ @ Nicol неправильный, находится внизу, если вы интересует только это).
Какая функция будет вызываться, определяется процессом, называемым разрешением перегрузки . Этот процесс довольно сложный, поэтому мы коснемся только той части, которая важна для нас.
Во-первых, важно посмотреть, как работает разрешение перегрузки для функций-членов:
§13.3.1 [over.match.funcs]
p2 Набор функций-кандидатов может содержать как функции-члены, так и функции, не являющиеся членами, которые должны быть разрешены для одного и того же списка аргументов. Чтобы списки аргументов и параметров были сопоставимы в этом гетерогенном наборе, считается , что функция-член имеет дополнительный параметр, называемый параметром неявного объекта, который представляет объект, для которого была вызвана функция-член . [...]
p3 Точно так же, когда это уместно, контекст может создать список аргументов, который содержит подразумеваемый аргумент объекта для обозначения объекта, с которым нужно работать.
Почему нам даже нужно сравнивать функции-члены и не-члены? Перегрузка оператора, вот почему. Учти это:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Вы наверняка хотели бы, чтобы следующее вызывало функцию free, не так ли?
char const* s = "free foo!\n";
foo f;
f << s;
Вот почему функции-члены и не-члены включены в так называемый набор перегрузки. Чтобы сделать решение менее сложным, существует жирная часть стандартной цитаты. Кроме того, это важный бит для нас (тот же пункт):
p4 Для нестатических функций-членов тип неявного параметра объекта:
«Lvalue ссылка на cv X
» для функций, объявленных без квалификатора ref или с &
квалификатором ref
«Rvalue ссылка на cv X
» для функций, объявленных с помощью &&
ref-qualifier
где X
класс, членом которого является функция, и 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
квалифицированным, значение r может быть привязано к параметру, если во всех других отношениях аргумент может быть преобразован в тип параметра неявного объекта.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
Теперь о том, почему ответ @ Nicol отчасти неверен. Он говорит:
Обратите внимание, что это объявление меняет тип *this
.
Это неправильно, *this
это всегда именующий:
§5.3.1 [expr.unary.op] p1
Унарный *
оператор выполняет косвенное обращение: выражение, к которому он применяется, должно быть указателем на тип объекта или указателем на тип функции, а результатом является lvalue, указывающее на объект или функцию, на которые указывает выражение.
§9.3.2 [class.this] p1
В теле нестатической (9.3) функции-члена ключевое слово this
является выражением prvalue, значением которого является адрес объекта, для которого вызывается функция. Тип this
функции-члена класса X
- X*
. [...]
MyType(int a, double b) &&
?Существует дополнительный вариант использования для формы ref-квалификатора lvalue. C ++ 98 имеет язык, который позволяет
const
вызывать функции, не являющиеся членами, для экземпляров классов, которые являются значениями. Это приводит к всевозможным странностям, которые противоречат самой концепции rvaluenness и отличаются от того, как работают встроенные типы:Ре-квалификаторы Lvalue решают эти проблемы:
Теперь операторы работают так же, как операторы встроенных типов, принимая только lvalue.
источник
Допустим, у вас есть две функции в классе, с одинаковым именем и подписью. Но один из них объявлен
const
:Если экземпляром класса не является
const
, разрешение перегрузки будет предпочтительно выбирать неконстантную версию. Если экземпляр естьconst
, пользователь может вызвать толькоconst
версию. Иthis
указатель являетсяconst
указателем, поэтому экземпляр не может быть изменен.«Ссылка на r-значение для этого» позволяет вам добавить еще одну альтернативу:
Это позволяет вам иметь функцию, которая может быть вызвана, только если пользователь вызывает ее через правильное значение r. Так что, если это в типе
Object
:Таким образом, вы можете специализировать поведение в зависимости от того, осуществляется ли доступ к объекту через r-значение или нет.
Обратите внимание, что вам не разрешается перегрузка между ссылочными версиями r-значения и не ссылочными версиями. То есть, если у вас есть имя функции-члена, во всех ее версиях либо используются квалификаторы l / r-value
this
, либо ни одна из них не использует. Вы не можете сделать это:Вы должны сделать это:
Обратите внимание, что это объявление меняет тип
*this
. Это означает, что&&
все версии имеют доступ к членам в качестве ссылок на r-значение. Таким образом, становится возможным легко перемещаться изнутри объекта. Пример, приведенный в первой версии предложения: (примечание: следующее может быть неверным с окончательной версией C ++ 11; это прямо из первоначального «r-значения из этого» предложения):источник
std::move
вторая версия, не так ли? Кроме того, зачем возвращать ссылку на rvalue?*this
, однако я могу понять, откуда возникла путаница. Это связано с тем, что ref-qualifier изменяет тип неявного (или «скрытого») параметра функции, с которым связан объект «this» (здесь ставятся цели!) Во время разрешения перегрузки и вызова функции. Таким образом, без изменений,*this
поскольку это исправлено, как объясняет Xeo. Вместо этого измените параметр «hiddden», чтобы сделать его ссылкой на lvalue или rvalue, точно так же, какconst
это делает квалификатор функции иconst
т. Д.