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


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

shared_ptr<Foo> foo = ...
shared_ptr<Bar> bar = ...
Эти интеллектуальные указатели являются оболочками вокруг ресурсов, которые по какой-то причине должны быть уничтожены в порядке foo, а затем bar. Теперь я хочу создать лямбду, которая использует foo и bar, но переживает область, содержащую их. Поэтому я бы захватил их по стоимости, например:
auto lambda = [foo, bar]() { ... };

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

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

4 12

4 ответа:

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

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

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

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

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

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

Однако вы не должны зависеть от разрушения лямбды. Вы должны быть в состоянии просто сбросить 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; ... };

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

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