Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should ownership be ended before or after stl containers call its value's destructor?

Tags:

c++

c++11

st

In the following code, an X is registered in a global container that becomes a shared owner of it. The destructor of X tests that it is no longer part of such ownership anymore, which I would expect to be a valid precondition for getting destroyed.

#include <vector>
#include <memory>

struct X {
    ~X();
};

std::vector<std::shared_ptr<X> > global_x_reg;

X::~X()
{
    for (auto iter = global_x_reg.begin(), end = global_x_reg.end(); iter != end; ++iter)
        if (iter->get() == this)
            throw "Oops. X gets destroyed while it is still owned!";
}

int main(int argc, char** argv)
{
    global_x_reg.push_back( std::shared_ptr<X>(new X) );
    global_x_reg.clear(); // calls X::~X().
}

When it runs (after compilation with VS2010), "Oops..." is thrown when the container is cleared.

Questions:

  1. is this code legal? If not, why not? If so, should it throw?
  2. should an std container implement clear() in such a way that during the destruction of its values, these values are no longer visible as containees.
  3. should std::shared_ptr::get, when std::shared_ptr is destroying its pointee, return nullptr?
like image 591
Maarten Hilferink Avatar asked Jun 26 '14 14:06

Maarten Hilferink


2 Answers

Per N3936 [basic.life]/1: "The lifetime of an object with non-trivial initialization ends when the destructor call starts.", and /3:

The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [ Note: In particular, before the lifetime of an object starts and after its lifetime ends there are significant restrictions on the use of the object, as described below, in 12.6.2 and in 12.7. Also, the behavior of an object under construction and destruction might not be the same as the behavior of an object whose lifetime has started and not ended. 12.6.2 and 12.7 describe the behavior of objects during the construction and destruction phases. —end note ]

You are invoking a member function on a shared_ptr after the end of its lifetime. Since there's no way to know if a given standard library class member function complies with the restrictions specified, invoking a member function on a standard library object after the end of its lifetime consequently has undefined behavior unless otherwise specified.

See also Library Working Group issue 2382 "Unclear order of container update versus object destruction on removing an object", which pertains very much to the question. In general, it's not a good idea to re-enter a standard library object (global_x_reg) while it is in the middle of processing a member function call (global_x_reg.clear() in this case). The class invariants obviously must hold before and after a member call, but there is no guarantee that the object is in a valid state during a call.

like image 93
Casey Avatar answered Oct 09 '22 10:10

Casey


I don't think there's any particular reason that .get() must return NULL before the deleter (and, consequently, ~X()) is invoked by std::shared_ptr<X>::~shared_ptr<X>. The only requirement is that it returns NULL once std:shared_ptr<X>::~shared_ptr<X> has completed.

Similarly, if std::vector is using placement new to construct and destroy its elements (and it will be), there's no reason that it must have updated its accessors before destroying a placed element.

In fact, if you think about this common old-fashioned pattern:

T* ptr = new T();
delete ptr;
ptr = NULL;

It's clear that ~T() will be invoked before ptr is set to NULL; this is pretty much the same thing that you're seeing.

The following extract from libstdc++ v4.6.3 supports the analogy:

00353       virtual void
00354       _M_destroy() // nothrow
00355       {
00356     _My_alloc_type __a(_M_del);
00357     this->~_Sp_counted_deleter();
00358     __a.deallocate(this, 1);
00359       }

(link)

In short, you're observing a totally harmless implementation detail and trying to assert that it breaks stated semantics, when I don't think that it does.

like image 35
Lightness Races in Orbit Avatar answered Oct 09 '22 11:10

Lightness Races in Orbit