Чому я не можу захопити це побічне посилання ('& this') у лямбда-записі?


91

Я розумію, як правильно зафіксувати this(змінити властивості об’єкта) в лямбда-режимі:

auto f = [this] () { /* ... */ };

Але мені цікаво наступну особливість, яку я бачив:

class C {
    public:
        void foo() {
            // auto f = [] () { // this not captured
            auto f = [&] () { // why does this work?
            // auto f = [&this] () { // Expected ',' before 'this'
            // auto f = [this] () { // works as expected
                x = 5;
            };
            f();
        }

    private:
        int x;
};

Дивина, якою мене бентежить (і я хотів би відповісти), полягає в тому, чому працює наступне:

auto f = [&] () { /* ... */ }; // capture everything by reference

І чому я не можу явно захопити thisпосиланням:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.

6
Чому ти хотів би? Що стосується речей, для яких посилання на вказівник може коли-небудь бути корисним: thisне може бути змінено, воно недостатньо велике, щоб зробити посилання швидшим ... і в будь-якому випадку , насправді воно не існує , тому воно є жодне реальне життя, тобто будь-яке посилання на нього буде звисати за визначенням. thisце перше значення, а не значення.
underscore_d

Відповіді:


111

Причина [&this]не працює, оскільки це синтаксична помилка. Кожен параметр, розділений комами, lambda-introducerє capture:

capture:
    identifier
    & identifier
    this

Ви бачите, що &thisце не дозволено синтаксично. Причина, через яку це не дозволено, полягає в тому, що ви ніколи не захочете зробити знімок thisза допомогою посилання, оскільки це невеликий покажчик const. Ви хотіли б коли-небудь передавати його за значенням - тому мова просто не підтримує захоплення thisза допомогою посилання.

Для thisявного захоплення ви можете використовувати [this]як lambda-introducer.

Першим captureможе бути такий, capture-defaultякий:

capture-default:
    &
    =

Це означає, що я автоматично використовую те, що я використовую, за посиланням ( &) або за значенням ( =) відповідно - однак лікування thisє особливим - в обох випадках воно фіксується значенням з причин, зазначених раніше (навіть із захопленням за замовчуванням &, що зазвичай означає захоплення за допомогою посилання).

5.1.2.7/8:

Для пошуку імен (3.4), визначення типу та значення this(9.3.2) та перетворення виразів id, що посилаються на нестатичні члени класу, у вирази доступу членів класу за допомогою (*this)(9.3.1), складений вираз [OF ЛАМБДА] розглядається в контексті лямбда-виразу.

Отже, лямбда діє так, ніби вона є частиною функції, що включає член, при використанні імен членів (як у вашому прикладі використання імені x), тому вона буде генерувати "неявні звички", thisяк це робить функція-член.

Якщо лямбда-захоплення включає захоплення за замовчуванням, тобто &ідентифікатори в ламбда-захопленні не повинні передувати &. Якщо лямбда-захоплення включає захоплення за замовчуванням, тобто =, лямбда-захоплення не повинно містити, thisі кожному ідентифікатору, який він містить, повинен передувати &. Ідентифікатор або thisне повинен з’являтися більше одного разу під час захоплення лямбда-сигналу.

Таким чином , ви можете використовувати [this], [&], [=]або [&,this]як lambda-introducerзахопити thisпокажчик на значення.

Однак [&this]і [=, this]погано сформовані. В останньому випадку gcc прощально попереджає про [=,this]це, explicit by-copy capture of ‘this’ redundant with by-copy capture defaultа не про помилки.


3
@KonradRudolph: Що робити, якщо ви хочете зафіксувати деякі речі за значенням, а інші за посиланням? Або хочете бути дуже чіткими з тим, що ви захоплюєте?
Xeo

2
@KonradRudolph: Це функція безпеки. Ви можете випадково захопити імена, які ви не збираєтеся робити.
Ендрю Томазос,

8
@KonradRudolph: Конструкції на рівні блоку магічним чином не копіюють вказівник на об'єкти, які вони використовують, у новий невидимий анонімний тип, який потім може пережити обширний обсяг - просто використовуючи ім'я об'єктів у виразі. Захоплення лямбди - це набагато більше небезпечного бізнесу.
Ендрю Томазос,

5
@KonradRudolph Я б сказав, "використовуйте, [&]якщо ви робите щось на зразок створення блоку для передачі в структуру управління", але явно фіксуйте, якщо ви створюєте лямбда, який буде використовуватися для менш простих цілей. [&]це жахлива ідея, якщо лямбда переживе нинішні рамки. Однак багато застосувань лямбда - це просто способи передачі блоків в керуючі структури, і блок не переживе блоку, який він створив за обсягом.
Якк - Адам Неврамонт

2
@Ruslan: Ні, thisце ключове слово, thisне є ідентифікатором.
Ендрю Томазос,

6

Оскільки стандарт не містить &thisу списках захоплення:

N4713 8.4.5.2 Захоплення:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer
  1. Для цілей лямбда-захоплення вираз потенційно посилається на локальні сутності таким чином:

    7.3 А цей вираз потенційно посилається на * це.

Отже, стандартні гарантії є thisі *thisдійсними, і &thisнедійсними. Крім того, захоплення thisозначає захоплення *this(що є значенням, сам об'єкт) за допомогою посилання , а не захоплення thisпокажчика за значенням !


*thisфіксує об'єкт за значенням
sp2danny
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.