C 11 лямбд: ошибка при захвате переменной-члена

#c #c 11

#c #c 11

Вопрос:

Рассмотрим этот код:

 #include <memory>
#include <iostream>

class A
{
public:
    A(int data) : data_(data)
    { std::cout << "A(" << data_ << ")" << std::endl; }
    ~A() { std::cout << "~A()" << std::endl; }
    void a() { std::cout << data_ << std::endl; }
private:
    int data_;
};

class B
{
public:
    B(): a_(new A(13)) { std::cout << "B()" << std::endl; }
    ~B() { std::cout << "~B()" << std::endl; }
    std::function<void()> getf()
    {
        return [=]() { a_->a(); };
    }
private:
    std::shared_ptr<A> a_;
};

int main()
{
    std::function<void()> f;
    {
        B b;
        f = b.getf();
    }
    f();
    return 0;
}
  

Здесь похоже, что я фиксирую a_ общий указатель по значению, но когда я запускаю его в Linux (GCC 4.6.1), это печатается:

 A(13)
B()
~B()
~A()
0
  

Очевидно, что 0 неверно, потому что A уже уничтожен. Похоже this , что он действительно захвачен и используется для поиска this->a_ . Мои подозрения подтверждаются, когда я меняю список захвата с [=] на [=,a_] . Затем выводится правильный вывод, и время жизни объектов соответствует ожидаемому:

 A(13)
B()
~B()
13
~A()
  

Вопрос:

Задано ли это поведение стандартом, определяется реализацией или не определено? Или я сумасшедший, и это что-то совершенно другое?

Комментарии:

1. Я думаю, что это по стандарту, поскольку a_ обычно разрешает this->a_, если вы явно не укажете ему копировать a_ .

2. Мне кажется, что это вполне законно — единственная переменная в области видимости this . Было бы удивительно , если бы указатель был просто волшебным образом разыменован на член! Тем не менее, хороший вопрос и отличное предупреждение для детей, чтобы они не использовали ленивые захваты [=] / [amp;] опрометчиво.

Ответ №1:

Задано ли это поведение стандартом

ДА. Захват переменных-членов всегда выполняется с помощью захвата this ; это единственный способ получить доступ к переменной-члену. В области видимости функция-член a_ эквивалентна (*this).a_ . Это верно и для лямбд.

Поэтому, если вы используете this (неявно или явно), вы должны убедиться, что объект остается активным, пока существует экземпляр lambda.

Если вы хотите захватить ее по значению, вы должны явно это сделать:

 std::function<void()> getf()
{
    auto varA = a_;
    return [=]() { varA->a(); };
}
  

Если вам нужна спецификация:

Составной оператор лямбда-выражения выдает тело функции (8.4 ) оператора вызова функции, но для целей поиска имени (3.4), определения типа и значения этого (9.3.2) и преобразования id-выражений, ссылающихся на нестатические члены класса, в выражения доступа к членам класса с использованием(* this) (9.3.1 ), составной оператор рассматривается в контексте лямбда-выражения.

Комментарии:

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

2. @Martin: Почему вы предупреждаете об этом? Если бы лямбда-выражение использовалось внутри алгоритма, это было бы прекрасно. Он столкнулся с проблемой только потому, что он вернул кому-то лямбду.

3. @Dani но возврат a члену не является ошибкой shared_ptr . Это похоже на потенциальную распространенную ошибку, поскольку я столкнулся с ней при преобразовании существующего кода. Я действительно думаю, что есть предупреждение (по крайней мере, включено -Wextra ), и его можно отключить, когда вы явно обращаетесь к члену this-> , например, через, или добавляете его в список захвата.

4. @Nicol: для меня проблема здесь не в том, что неприятный старый C позволяет мне стрелять себе в ногу, проблема в том, что «захват по значению» лямбды автоматически захватывает this , а не _a . Это стоит дополнительного предупреждения, IMO, независимо от того, возвращается лямбда или нет. Если бы предупреждение побуждало вас писать (*this)._a явно, тогда было бы очевидно, что это this то, что захвачено. Компиляторы предупреждают о других вещах, которые гораздо менее опасны и менее тонко некорректны.

5. @Nicol: я не хочу никого «наказывать», я думаю, что необязательное предупреждение оправдано. Вам не нужно ее включать, поэтому я нахожу удивительным, что вы настолько увлечены, что она даже не должна быть доступна для других. Вы находите очевидным, что [=] захватывает this , а не _a потому, что вы полностью знакомы со стандартом в этой области. Это здорово, но компиляторы предупреждают о гораздо менее сложных тонкостях языка.