Що таке "посилання на оцінку для цього *?"


238

На сторінці статусу C ++ 11 кланг потрапила пропозиція під назвою "rvalue reference for * this" .

Я читав досить багато про посилання на оцінку і розумів їх, але не думаю, що знаю про це. Я також не міг знайти багато ресурсів в Інтернеті, використовуючи умови.

На сторінці розміщено посилання на документ із пропозиціями: N2439 (Розширення семантики переміщення до * цього), але я також не отримую звідси багато прикладів.

Про що ця особливість?

Відповіді:


293

По-перше, "кваліфікаційні кваліфікації для цього" - це просто "маркетингова заява". Тип *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

Все це робиться для того, щоб ви могли скористатися тим фактом, коли об'єктом, за яким викликається функція, є ревальвінг (наприклад, неназваний тимчасовий). Візьміть наступний код як додатковий приклад:

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-kvalifiers ( &і &&).


Примітка: Багато стандартних цитат та пояснення роздільної здатності після перевантаження тут!

† Щоб зрозуміти, як це працює, і чому відповідь @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

Ви, звичайно, хочете, щоб зателефонували до безкоштовної функції, чи не так?

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

Ось чому функції членів і не членів включаються в так званий набір перевантажень. Щоб зробити резолюцію менш складною, існує смілива частина стандартної цитати. Крім того, це важливий біт для нас (той самий пункт):

p4 Для нестатичних функцій члена тип імпліцитного параметра об'єкта

  • «Саме посилання на сорти X » для функцій , оголошена без рефов-класифікатором , або з & рефом-класифікатором

  • "Rvalue посилання на cv X " для функцій, оголошених за допомогою ref- kvalifier&&

де 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

Однак тут слід зазначити , що, якщо б ми не представили жодних реф-специфікатор (і як такої не перевантажена функцію), яка f1 буде збігатися з RValue ( до сих пір §13.3.1):

p5 [...] Для нестатичних функцій-членів, оголошених без перекваліфікатора , застосовується додаткове правило:

  • навіть якщо параметр неявного об'єкта не є constкваліфікованим, rvalue може бути прив'язаний до параметра, доки в усіх інших відношеннях аргумент може бути перетворений на тип неявного параметра об'єкта.
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

Одинарний *оператор виконує непряме : вираз, до якого він застосовується, має бути вказівником на тип об'єкта або вказівником на тип функції, а результат - значенням, що посилається на об'єкт або функцію, на яку вказує вираз.

§9.3.2 [class.this] p1

У тілі нестатичної (9.3) функції члена ключове слово thisє першим виразом, значенням якого є адреса об'єкта, для якого викликається функція. Тип функції thisу члені класу Xє X*. [...]


Я вважаю, що типи параметрів відразу після розділу "після перетворення" мають бути "foo", а не "test".
ryaner

@ryaner: Добре знайдіть, дякую. Хоча не параметр, а ідентифікатор класу функцій неправильний. :)
Xeo

ой вибачте, що я забув про клас іграшок з іменем тесту, коли прочитав цю частину і подумав, що f міститься в foo, таким чином мій коментар ..
ryaner

Чи можна це зробити за допомогою конструкторів MyType(int a, double b) &&:?
Герман Діаго

2
"Тип * це ніколи не змінюється" Можливо, вам повинно бути трохи зрозуміліше, що він не змінюється на основі кваліфікації значення r / l. але він може змінюватися між const / non-const.
xaxxon

78

Існує додатковий випадок використання для форми кваліфікаційного коефіцієнта lvalue. C ++ 98 має мову, яка дозволяє constвикликати функції, які не є членами, для екземплярів класу, що є rvalues. Це призводить до будь-яких дивацтв, що суперечать самій концепції рентабельності та відхиляються від того, як працюють вбудовані типи:

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

Тепер оператори працюють як ті, що вбудовані типи, приймаючи лише значення.


28

Скажімо, у вас є дві функції в класі, обидві з тим же ім'ям та підписом. Але одна з них заявлена const:

void SomeFunc() const;
void SomeFunc();

Якщо екземпляр класу відсутній const, роздільна здатність перевантаження бажано буде вибирати версію, яка не є 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 this, або жодна з них не робить. Ви не можете цього зробити:

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

Ви повинні зробити це:

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

Зауважте, що ця декларація змінює тип *this. Це означає, що &&всі версії мають доступ до членів як посилання r-значення. Так стає можливим легко переміщатися зсередини об'єкта. Приклад, наведений у першій версії пропозиції, є (зверніть увагу: наступна може бути невірною для остаточної версії C ++ 11; це прямо від початкового "r-значення цієї пропозиції"):

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

2
Я думаю, вам потрібна std::moveдруга версія, чи не так? Крім того, чому повертається посилання rvalue?
Ксео

1
@Xeo: Тому що саме такий приклад був у пропозиції; Я поняття не маю, чи все ще працює з поточною версією. А причина повернення опорного значення r полягає в тому, що рух повинен відповідати особі, яка його захоплює. Це ще не повинно статися, на всякий випадок, якщо він насправді хоче зберегти його в && замість значення.
Нікол Болас

3
Правильно, я якось подумав про причину свого другого питання. Цікаво, що ж, чи оцінює посилання на члена тимчасового члена тимчасовий термін дії цього члена або його члена? Я міг би поклятися, що я побачив питання про це на SO деякий час тому ...
Xeo

1
@Xeo: Це не зовсім правда. Роздільна здатність перевантаження завжди вибирає неконст-версію, якщо вона існує. Вам потрібно буде зробити акторський склад, щоб отримати версію const. Я оновив публікацію, щоб уточнити.
Нікол Болас

15
Я подумав, що можу пояснити, адже я створив цю функцію для C ++ 11;) Xeo має рацію, наполягаючи, що вона не змінює тип *this, проте я можу зрозуміти, звідки виникає плутанина. Це тому, що ref-kvalifier змінює тип неявного (або "прихованого") параметра функції, до якого об'єкт "this" (цитати, призначені тут!) Прив'язується під час вирішення перевантаження та виклику функції. Отже, жодних змін, *thisоскільки це виправлено, як пояснює Xeo. Замість того, щоб змінити параметр "прихований", щоб зробити його посиланням на lvalue- або rvalue, подібно до того, як constкласифікатор функції робить його constтощо.
bronekk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.