Std:: функции и передача лямбда-функции

У меня есть класс, который принимает std::function как параметр, который я назначаю лямбда-функции. Он работает в конструкторе, но после этого он перестает работать. Отладчик говорит, что f является "пустым" после запуска первой строки. Почему?

#include <iostream>
#include <string>
#include <functional>
typedef std::function<void(std::string)> const& fn;
class TestClass
{
public:
 TestClass(fn _f) : f(_f) { F(); }
 void F() { f("hello"); };
private:
 fn f;
};
int main()
{
 TestClass t([](std::string str) {std::cout << str << std::endl; });
 t.F();
 return 0;
}
</void(std::string)></functional></string></iostream>

Вызов t.F() вызывает ошибку. Почему?

Я могу решить это, изменив его на следующее:

int main()
{
 fn __f = [](std::string str) {std::cout << str << std::endl; };
 TestClass t(__f);
 t.F();
 return 0;
}

но опять же, это не работает, когда я меняю fn на auto!

int main()
{
 auto __f = [](std::string str) {std::cout << str << std::endl; };
 TestClass t(__f);
 t.F();
 return 0;
}

Какое объяснение, почему это происходит?

2 ответа

Обратите внимание, что (1) fn определяется как ссылка (для константы); (2) лямбда и std::function не являются одинаковыми; (3) Вы не можете напрямую привязать ссылку к объекту с другим типом.

В первом случае

TestClass t([](std::string str) {std::cout << str << std::endl; });
t.F();

Создается временная лямбда, а затем преобразуется в std::function, которая также является временной. Временный std::function привязан к параметру _f конструктора и привязан к элементу f. Временное будет уничтожено после этого утверждения, тогда f станет болтаться, когда t.F(); он терпит неудачу.

Для второго случая

fn __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();

Создается временная лямбда, а затем привязывается к ссылке (to const). Тогда его время жизни увеличивается до времени жизни ссылки __f, поэтому код в порядке.

В третьем случае

auto __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();

lambda создается, а затем преобразуется в std::function, который является временным. Временный std::function привязан к параметру _f конструктора и привязан к элементу f. Временное будет уничтожено после этого утверждения, тогда f станет болтаться, когда t.F(); он терпит неудачу.

(1) Вы можете объявить fn как ссылку, например typedef std::function<void(std::string)> fn;</void(std::string)>, тогда std::function будет скопирована, и каждый случай будет работать хорошо. (2) Не использовать имена начинаются с двойного подчеркивания, они зарезервированы на С++.


typedef std::function<void(std::string)> const& fn;
</void(std::string)>

Это не std::function, это ссылка на std::function.

TestClass(fn _f) : f(_f) { F(); }
fn f;

Здесь вы берете const& до std::function и привязываете его к другому const& к std::function. F() в теле конструктора работает, поскольку ссылка действительна, по крайней мере, до тех пор, пока существует конструктор.

TestClass t([](std::string str) {std::cout << str << std::endl; });

Это создает временную структуру std::function, созданную из лямбда. Это временное значение сохраняется до текущей строки (до ;).

Затем временная std::function отбрасывается.

Поскольку TestClass принимает std::function на const&, он не продлевает время жизни во времени.

Итак, после строки любой вызов std::function const& - это undefined поведение, которое вы видите в вызове .F() позже.

fn __f = [](std::string str) {std::cout << str << std::endl; };

Это продление срока действия ссылки. Временной std::function, созданный из лямбда, имеет время жизни, расширенное до времени жизни переменной __f.

В стороне, эта строка также делает вашу программу плохо сформированной, без необходимости диагностики, имея переменную, содержащую двойной знак подчеркивания. Такие идентификаторы зарезервированы для реализации компилятора, вы не можете их создавать.

TestClass t(__f);

Затем мы передаем эту ссылку (ссылаясь на временную расширенную продолжительность жизни), и все работает.

auto __f = [](std::string str) {std::cout << str << std::endl; };

Это создает переменную __f (см. выше, неправильное имя), которая является лямбдой.

Лямбда не является std::function. A std::function может быть создан из лямбды неявно.

TestClass t(__f);

Это создает временный std::function из лямбда, передает его в конструктор TestClass, а затем уничтожает временную.

После этой строки вызов .F() заканчивается после оборванной ссылки, а результаты поведения undefined.

Ваша основная проблема может заключаться в том, что вы считаете, что лямбда - это std::function. Это не. A std::function может хранить лямбда.

Вторая проблема заключается в том, чтобы наложить что-то как const&, что почти всегда является действительно глупой идеей. Ссылки ведут себя по-другому, чем значения фундаментальными способами.

Ваша третья проблема - двойное сокращение в ваших именах переменных. (Или идентификатор, начинающийся с _, за которым следует заглавная буква).

Если вы хотите знать, как работает std::function и что это такое, есть много хороших сообщений SO по этому вопросу с различными уровнями технической детализации.

licensed under cc by-sa 3.0 with attribution.