Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 316
Daniel Wolf Avatar asked Sep 20 '12 20:09

Daniel Wolf


People also ask

What is a lambda capture list?

The capture list defines the outside variables that are accessible from within the lambda function body. The only capture defaults are. & (implicitly capture the used automatic variables by reference) and. = (implicitly capture the used automatic variables by copy).

What is the correct syntax for lambda expression in C++11?

Lambdas can both capture variables and accept input parameters. A parameter list (lambda declarator in the Standard syntax) is optional and in most aspects resembles the parameter list for a function. auto y = [] (int first, int second) { return first + second; };

What is capture in lambda function?

A lambda expression can refer to identifiers declared outside the lambda expression. If the identifier is a local variable or a reference with automatic storage duration, it is an up-level reference and must be "captured" by the lambda expression.

What is the type of a lambda?

A type lambda lets one express a higher-kinded type directly, without a type definition. For instance, the type above defines a binary type constructor, which maps arguments X and Y to Map[Y, X] . Type parameters of type lambdas can have bounds, but they cannot carry + or - variance annotations.


2 Answers

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.

like image 95
Nicol Bolas Avatar answered Oct 29 '22 23:10

Nicol Bolas


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; ... };
like image 26
Lily Ballard Avatar answered Oct 29 '22 21:10

Lily Ballard