Является ли поведение undefined разрушением / удалением std :: function в середине вызова?
class Event {
public:
Event(std::function<void()> f) : func(std::move(f)) {}
~Event() {}
std::function<void()> func;
};
int main()
{
std::vector<Event> events;
auto func = [&]() {
events.pop_back();
std::cout << "event" << std::endl;
// do more work
};
events.emplace_back(std::move(func));
events[0].func();
return 0;
}
Вы ничего не делаете «посередине». Все, что вы делаете, упорядочено.
Я уверен, что это UB, если вы обращаетесь к объектам в захвате лямбда после его уничтожения, поскольку захваченные объекты также были бы уничтожены. Я не уверен в общем случае.
// do more work
использует какие-либо члены лямбды?
Возможный дубликат: stackoverflow.com/questions/22998364/…
Ваш код (бит, о котором вы спрашиваете, т.е. разрушение объекта внутри функции-члена) примерно эквивалентен
struct A
{
void f() { delete this; }
}
int main()
{
A* a = new A;
a.f();
}
Это действительно работает. Ресурсы с пересчетом могут делать нечто подобное, когда счетчик ссылок достигает нуля в их функции unref
.
Обратите внимание, что вы можете захотеть переосмыслить связывание списка событий и самих событий таким образом. Событие не должно знать о своем окружении (очереди событий).
Это работает, когда вы не получаете доступ к участникам A
после delete
. Есть ли гарантия, что std::function
не получит доступ к своим собственным членам после вызова вызываемого объекта?
@interjay Это законно (поскольку в нем не будет UB, не уверен, что стандарт действительно позволяет это делать или нет) для std::function
делать с функтором все, что он хочет. В его деструкторе функтор еще не уничтожен. Что было бы UB b f()
с использованием некоторого состояния из A
после того, как он сделал delete this;
Контрпример: представьте, что вектор был захвачен по стоимости. Тогда у нас есть такой случай, по крайней мере, потенциально, поскольку вектор будет удален прямо в середине вызова pop_back. Только если вызов деструктора был последним, что делает pop_back (на что у нас нет никаких гарантий и что тоже маловероятно), мы могли бы обойтись без UB ...
Это не определено [res.on.objects] p2:
If an object of a standard library type is accessed, and the beginning of the object's lifetime does not happen before the access, or the access does not happen before the end of the object's lifetime, the behavior is undefined unless otherwise specified.
«Доступ» в этом случае состоит из вызова оператора вызова функции std::function
. Время жизни объекта std::function
закончилось при вызове pop_back()
в середине доступа. Следовательно, доступ не происходит до окончания времени жизни объекта, и поведение не определено.
Обсуждался когда-то случай, когда объект удалял себя в одной из своих функций. Тогда здравый смысл заключался в том, что это нормально, если функция больше не имеет доступа к каким-либо членам после. Справочная информация: код функции и сам объект находятся в разных местах, поэтому, даже если внутри функции-члена, чистый вызов не имеет доступа к объекту. Однако критичным здесь является ссылка на вектор. Но он копируется (ссылка, а не вектор ...) в аргумент pop_back, поэтому не должно быть проблем (хотя бы, если бы вектор был захвачен стоимость !!!).
Если обсуждение тех дней был верное, то удаление из вектора должно быть прекрасным. если и только если - это последнее действие, которое ссылается на какие-либо элементы объекта (то есть элементы закрытия). В противном случае это означало бы, что самоудаление из функции вообще невозможно ...
Неважно, что говорит ядро. Вы используете библиотечный компонент, и вам необходимо соблюдать правила библиотеки.
И где я не делаю этого, учитывая вышеизложенные соображения? Вектор захватывается по ссылке, ссылка передается в pop_back. Хорошо, this
pop_back указывает на вектор в main. В какой-то момент вызывается деструктор лямбды. Отлично. Деструктор не удаляет вектор, а только ссылку. Отлично. Лямбда больше не имеет доступа к захваченным членам. Отлично. Итак, в чем проблема (если вообще самоудаление не UB!)?
Хм, хорошо; есть // do more work comment
- если там будет какой-то членский доступ ... UB!
Вы разрушаете не только лямбду. Вы уничтожаете std::function
, который является компонентом библиотеки и поэтому следует правилам библиотеки. (IOW, разработчикам стандартной библиотеки не требуется наклоняться назад, чтобы терпеть такие махинации.) Это UB, даже если лямбда возвращается сразу после pop_back
.
Хм, std::function
как таковой не должен быть критичным, это просто обычный член класса Event, содержащий (копию оригинального) лямбда, и, таким образом, разрушается вместе с Event. Но я думаю, что начинаю догонять: критический момент заключается в том, что мы находимся где-то в пределах std::function
operator()
, и у нас, вероятно, нет никаких гарантий, что он не получит доступа ни к одному из своих членов после вызова лямбды, поэтому мы, по крайней мере, потенциально заканчивают неопределенным поведением, не так ли?
Из cppreference: "Эффективно работает INVOKE<R>(f, std::forward<Args>(args)...)
" - достаточно ли этого для введения вышеупомянутой гарантии?
Неважно. Там есть общая формулировка библиотеки, в которой говорится, что доступ к объекту стандартной библиотеки после запуска его деструктора - это UB. Это заменяет все правила основного языка в отношении стандартной библиотеки.
Найдено в [defns.access]: «доступ к 〈действию времени выполнения〉 для чтения или изменения значения объекта». Моя проблема теперь заключается в следующем: с этим определением я просто не могу видеть, где функция std :: все еще является доступ, если возможно, что operator()
не обращается к собственным членам после вызова лямбда. Если нет, то UB нет. В целях безопасности мы должны рассматривать потенциал UB точно так же, как UB на самом деле, пока я полностью с вами. Итак, наконец, я в третий раз возвращаюсь к уже упомянутой гарантии: есть она у нас или нет?
Не хочу показаться упрямым, просто: у нас есть два противоречащих друг другу ответа: один - ОК, другой - UB. В настоящее время я не могу себе представить, где на самом деле (или только потенциально) происходит UB, если только мы не можем полагаться на то, что вызов лямбды является последним действием в std::function::operator()
, поэтому я такой упорный (эй, у меня есть партия для ...) на тема, за то, что я смог наконец принять собственное мнение ...
ОТ: почему бы не сделать так, чтобы об этом позаботилось что-то другое, возможно, после вызова? Вы действительно хотите писать
pop_back
в каждом обработчике событий?