Подтвердить что ты не робот

С++ 11: В каком порядке уничтожены лямбда-захваты?

Скажем, у меня есть два локальных интеллектуальных указателя, foo и bar.

shared_ptr<Foo> foo = ...
shared_ptr<Bar> bar = ...

Эти интеллектуальные указатели представляют собой оболочки вокруг ресурсов, которые по какой-то причине должны быть разрушены в порядке foo, а затем bar.

Теперь я хочу создать лямбду, которая использует foo и bar, но переживает область, содержащую их. Поэтому я бы захватил их по значению, например:

auto lambda = [foo, bar]() { ... };

Это создает копии foo и bar внутри объекта функции. Когда объект функции будет разрушен, эти копии также будут уничтожены, но я забочусь о порядке, в котором это происходит. Поэтому мой вопрос:

Когда объект лямбда разрушен, в каком порядке уничтожаются его значения по величине? И как я могу (надеюсь) повлиять на этот порядок?

4b9b3361

Ответ 1

Спецификация охватывает это... вроде. Из пункта 5.1.2, пункт 14:

Сущность захватывается копией, если она неявно зафиксирована, а значение по умолчанию - = или если оно явно захвачено с захватом, который не включает &. Для каждого объекта, захваченного копией, в типе закрытия объявляется неназванный нестатический элемент данных. Порядок объявления этих членов не указан.

Добавлен акцент. Поскольку заказ объявления не указан, заказ на строительство не указан (так как порядок построения совпадает с порядком объявления). И поэтому порядок уничтожения неуточнен, так как порядок разрушения является обратным порядку постройки.

Короче говоря, если вам нужно соблюдать порядок объявления (и различные приказы о строительстве/уничтожении, которые отключают его), вы не можете использовать лямбда. Вам нужно будет создать свой собственный тип.

Ответ 2

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

Ответ 3

Как говорит Николь, порядок уничтожения неуточнен.

Однако вам не следует зависеть от разрушения лямбды. Вы должны иметь возможность просто reset foo в конце вашей лямбды, тем самым гарантируя, что он освободит свой ресурс до bar. Вам также придется отмечать лямбда как mutable. Единственный недостаток здесь - вы не можете многократно называть лямбда и ожидать, что он будет работать.

auto lambda = [foo, bar]() mutable { ...; foo.reset(); };

Если вам нужно, чтобы ваш лямбда вызывался несколько раз, вам нужно придумать другой способ контроля порядка освобождения. Одним из вариантов было бы использовать промежуточную структуру с известным порядком членов данных, например, std::pair<>:

auto p = std::make_pair(bar, foo);
auto lambda = [p]() { auto foo = p.second, bar = p.first; ... };

Ответ 4

В соответствии с документом С++ 11 у меня есть (то есть халява, немного предшествующая ратификации n3242), раздел 5.1.2, параграф 21, захваты строятся в порядке объявления и разрушаются в обратном порядке объявления. Однако порядок объявления не указан (пункт 14). Таким образом, ответ: "в неуказанном порядке" и "вы не можете влиять на него" (за исключением, я полагаю, написания компилятора).

Если бар действительно нужно уничтожить до foo, было бы разумным, если бы бар содержал общий указатель на foo (или что-то в этом роде).