Что такое "ссылка на значение для * этого"? - PullRequest
229 голосов
/ 23 декабря 2011

Наткнулся на предложение под названием "ссылка на rvalue для * this" на странице статуса C ++ 11 .

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

На странице есть ссылка на документ с предложением: N2439 (Расширение семантики перехода на * this), но яя также не получаю много примеров оттуда.

О чем эта функция?

Ответы [ 3 ]

283 голосов
/ 23 декабря 2011

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

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

// 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-qualifier или с & ref-qualifier

  • «rvalue ссылка на cv X» для функций, объявленных с квалификатором &&

где 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-qualifier (и, следовательно, не перегружали функцию), то f1 будет соответствовать значению r (по-прежнему §13.3.1) ):

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

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

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}

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

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

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

§5.3.1 [expr.unary.op] p1

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

§9.3.2 [class.this] p1

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

77 голосов
/ 23 декабря 2011

Существует дополнительный вариант использования формы квалификатора lvalue. В C ++ 98 есть язык, который позволяет вызывать не-1001 * функции-члены для экземпляров классов, которые являются значениями. Это приводит ко всем видам странностей, которые противоречат самой концепции 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...

Refvalififier Lvalue решает эти проблемы:

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

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

28 голосов
/ 23 декабря 2011

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

void SomeFunc() const;
void SomeFunc();

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

Что делает "ссылка на r-значение для этого`, так это позволяет вам добавить еще одну альтернативу:

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-значения иверсии-ссылки, то есть, если у вас есть имя функции-члена, все ее версии либо используют квалификаторы l / r-value для this, либо ни одна из них не делает. Вы не можете сделать это:

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

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

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

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

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move
...