简体   繁体   中英

C++11: In what order are lambda captures destructed?

Let's say I have two local smart pointers, foo and bar .

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

These smart pointers are wrappers around resources that for some reason must be destructed in the order foo , then bar .

Now I want to create a lambda that uses foo and bar , but outlives the scope containing them. So I'd capture them by value, like this:

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

This creates copies of foo and bar within the function object. When the function object is destructed, these copies will be destructed, as well, but I care about the order in which this happens. So my question is:

When a lambda object is destructed, in what order are its by-value captures destructed? And how can I (hopefully) influence this order?

The spec covers this... sort of. From 5.1.2, paragraph 14:

An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an &. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified.

Emphasis added. Because the declaration order is unspecified, the construction order is unspecified (since the order of construction is the same as the order of declaration). And therefore, the destruction order is unspecified, since the order of destruction is the reverse of the order of construction.

In short, if you need to care about declaration order (and the various construction/destruction orders that key off of it), you cannot use a lambda. You'll need to make your own type.

Rather than worrying on what the order of destruction will be, you should fix the fact that this is a problem. Noting that you are using shared pointers for both objects, you can ensure the order of destruction by adding a shared pointer in the object that you need to outlive the other. At that point whether foo or bar is destroyed earlier will not matter. If the order is correct, destruction of the shared pointer will release the objects immediately. If the order is incorrect the additional shared pointer will maintain the object alive until the other goes away.

As Nicol says, the order of destruction is unspecified.

However, you shouldn't have to depend on the destruction of the lambda. You should be able to simply reset foo at the end of your lambda, thus ensuring it releases its resource before bar does. You'll also have to mark the lambda as mutable though. The only downside here is you can't call the lambda multiple times and expect it to work.

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

If you do need your lambda to be callable multiple times, then you need to come up with some other way to control the order of deallocation. One option would be to use an intermediate structure with a known data member order, such as a std::pair<> :

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

According to the C++11 document I have (ie the freebie, slightly-prior-to-ratification n3242), section 5.1.2, para 21, the captures are constructed in declaration order and destructed in reverse declaration order. However, declaration order is unspecified (paragraph 14). So the answer is, "in unspecified order" and "you cannot influence it" (except, I suppose, by writing a compiler).

If bar really needs to be destructed before foo, it would be wise for bar to hold a shared pointer to foo (or something of the kind).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM