C++ std::tuple order of destruction
Asked Answered
G

3

52

Is there a rule which states in which order the members of an std::tuple are destroyed?

For example if Function1 returns an std::tuple<std::unique_ptr<ClassA>, std::unique_ptr<ClassB>> to Function2, then can I be sure that (when the scope of Function2 is left) the instance of ClassB referred to by the second member is destroyed before the instance of ClassA referred to by the first member?

std::tuple< std::unique_ptr< ClassA >, std::unique_ptr< ClassB > > Function1()
{
    std::tuple< std::unique_ptr< ClassA >, std::unique_ptr< ClassB > > garbage;
    get<0>(garbage).reset( /* ... */ );
    get<1>(garbage).reset( /* ... */ );
    return garbage;
}

void Function2()
{
    auto to_be_destroyed = Function1();
    // ... do something else

    // to_be_destroyed leaves scope
    // Is the instance of ClassB destroyed before the instance of ClassA?
}
Geaghan answered 21/8, 2016 at 20:25 Comment(4)
I am guessing it mostly depends on how std::tuple is implemented in your standard library.Osteoma
I can't find anything in the spec that specifies the order of destruction for std::tuple. Probably should be filed as unspecified.Sung
https://mcmap.net/q/131385/-why-does-libstdc-store-std-tuple-elements-in-reverse-orderSchnauzer
This issue has very recently come up in Antony Polukhin's excellet CPPCon 2016 talk on achieving partial reflection in C++14 without proper language support for it. IIRC he says the only scary thing he had to do reimplement std::tuple to enforce a certain order of initialization. Also, ping @Sung on this.Gomuti
S
38

The standard doesn't specify the order of destruction for std::tuple. The fact that §20.4.1/p1 specifies that:

An instantiation of tuple with two arguments is similar to an instantiation of pair with the same two arguments.

Similar here is not interpreted as identical and consequently it's not implied that std::tuple should have a reverse destruction order of its arguments.

Given the recursive nature of std::tuple most probable is that the order of destruction is in order with the order of its arguments.

I also base my assumptions on a bug report for GCC BUG 66699 where in the discussion my assumptions above are justified.

That said, the order of destruction for std::tuple is unspecified.

Sung answered 21/8, 2016 at 21:6 Comment(0)
G
61

I'll offer a life lesson I've learned, rather than a direct answer, in response to your question:

If you can formulate, for multiple alternatives, a reasonable argument for why that alternative should be the one mandated by the standard - then you should not assume any of them is mandated (even if one of them happens to be).

In the context of tuples - please, please be kind to the people maintaining your code and do not allow the destruction order of tuple elements to potentially mess up the destruction of other elements. That's just evil... imagine the hapless programmer who will need to debug this thing. In fact, that poor soul might be yourself in a few years, when you've already forgotten about your clever trick from back-in-the-day.

If you absolutely must rely on destruction order, perhaps you should just be using a proper class with the tuple's elements as its data members (which you could write a destructor for, making it clear what needs to happen in what order), or some other arrangement facilitating a more explicit control of the destruction.

Gomuti answered 21/8, 2016 at 20:50 Comment(6)
This is a really important lesson! When programming in C++, this tip has always to be on developers minds.Conservatory
I don't quite follow this. Since C++11 strings, are required to have contiguous storage. But there's a reasonable argument (the one used when C++03 was written) not to require this. Or, if you dispute that example, pick another where the standard made a judgment call that could reasonably go either way. So should I not write code that relies on things that C++11 or C++14 tightened up? Or do you mean that if the standard isn't clear, then don't rely on the next programmer to have spit hairs the same way you did?Chippewa
Or for an intentionally silly example, there's a reasonable argument that std::vector should be called std::dynamic_array, and std::valarray should be called std::vector. But I think it's very reasonable to write code on the basis that std::vector is what the standard happens to say it is: we can't use it without assuming that. So I don't think I've understood what situations your advice applies to ;-)Chippewa
@SteveJessop: About the strings example - note that all code that does not assume strings have contiguous storage works in C++03 as well (ok, assuming it doesn't rely on C++11 features of course). And if you want to rely on contiguous storage, then do so because there's a good reason. Think of how little the code <algorithm> assumes, and how widely applicable it is. Still ok, I concede you could prefix my life lesson with "All other things being equal...".Gomuti
@SteveJessop: As for your silly example, obviously you can't not make assumptions about the API of the library you're using. But if you saw a co-worker of yours had written a file named "vector.hpp", I would not rush to assume exactly what kind of class s/he has in there.Gomuti
As an addendum: If you do end up relying on a clever trick like this, please, please document exactly what you are doing with comments. Describe the behaviour you're exploiting, and why you're exploiting it, in as much detail as you can, so that anyone who has to maintain it knows what they're dealing with.Grisly
S
38

The standard doesn't specify the order of destruction for std::tuple. The fact that §20.4.1/p1 specifies that:

An instantiation of tuple with two arguments is similar to an instantiation of pair with the same two arguments.

Similar here is not interpreted as identical and consequently it's not implied that std::tuple should have a reverse destruction order of its arguments.

Given the recursive nature of std::tuple most probable is that the order of destruction is in order with the order of its arguments.

I also base my assumptions on a bug report for GCC BUG 66699 where in the discussion my assumptions above are justified.

That said, the order of destruction for std::tuple is unspecified.

Sung answered 21/8, 2016 at 21:6 Comment(0)
O
16

With Clang 3.4 I get the same destruction order for both std::pair and 2 element std::tuple and with g++ 5.3 I get opposite order which could be mainly due to the recursive implementation of std::tuple in libstd++.

So, it basically boils down to what I said in the comment, it is implementation defined.

From the BUG report:

Comment by Martin Sebor

Since the layout of std::pair members is fully specified, so is the order of their initialization and destruction. The output of the test case reflects this order.

The order of initialization (and destruction) of std:stuple subobjects is less clearly specified. At least it's not immediately obvious from my reading of the spec if any particular order is required.

The reason why the output for std::tuple with libstdc++ is the reverse of std::pair is because the implementation, which relies on recursive inheritance, stores and constructs tuple elements in the reverse order: i.e., the base class, which stores the last element, is stored and constructed first, followed by each derived class (each of which stores the last - Nth element).

The quote from standard [section 20.4.1] which the bug reporter is quoting to

1 This subclause describes the tuple library that provides a tuple type as the class template tuple that can be instantiated with any number of arguments. Each template argument specifies the type of an element in the tuple. Consequently, tuples are heterogeneous, fixed-size collections of values. An instantiation of tuple with two arguments is similar to an instantiation of pair with the same two arguments. See 20.3.

Argument against this made in the linked bug is:

Being described as similar doesn't imply they are identical in every detail. std::pair and std::tuple are distinct classes with different requirements on each. If you believe the are required to behave identically in this respect (i.e., have their subobjects defined in the same order) you need to point to the specific wording that guarantees it.

Osteoma answered 21/8, 2016 at 20:37 Comment(8)
How do you know that the order of destruction of std::pair's elements is in the reverse?Successful
Similar != idententical.Sung
@deepmax Based on the implementation on both libcxx and libstd++ for std::pair.Osteoma
@juanchopanza Doesn't instantiation of a pair necessarily mean instantiation of its members ?Osteoma
Rebuild this answer around the information in the link please!Ladanum
@juanchopanza Yeah, so I think the wording similar in the standard is bit confusing here..could be argued both ways (?) not sure.Osteoma
@Sung I dunno, I think identical is similar to similar.Ladanum
@yakk very similar to identical but not identical to similar :)Sung

© 2022 - 2024 — McMap. All rights reserved.