Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens if I reset a std::shared_ptr to itself

The following program crashes with a bad glibc double free error:

#include <iostream>
#include <memory>

class foo {
public:
   foo()
   {
      std::cout << "foo constructed" << std::endl;
   }

   ~foo()
   {
      std::cout << "foo destructed" << std::endl;
   }
};

int main() {
   auto f = std::make_shared< foo >();
   std::cout << "Before reset" << std::endl;
   f.reset( f.get() );
   std::cout << "After reset" << std::endl;
   return 0;
}

From this I get the following output (followed by the glibc error):

foo constructed
Before reset
foo destructed
After reset
foo destructed

So obviously in this case the object is destroyed twice. Once by the reset and once by the std::shared_ptr going out of scope. This is actually what I would have expected.

On cppreference however I find the following text (found at http://en.cppreference.com/w/cpp/memory/shared_ptr/reset ):

If *this already owns an object and it is the last shared_ptr owning it, the object is destroyed through the owned deleter, unless ptr is a pointer to it.

In my opinion this actually says, that the object should not be destroyed as in my example. Quite surprising, but if the standard says so. Am I somehow misreading this, or is the implementation of std::shared_ptr I have available not conforming to the standard?

For those who ask why I am doing this:

I am currently trying to figure out how to temporarily manage bare pointers to objects created by new and new[]. The idea is to use the std::shared_ptr::reset() to replace the deleter with a no-op deleter. The alternative is to wrap the code with a try { stuff() } catch( ... ) { delete x; throw;} kind of block.

like image 545
LiKao Avatar asked Mar 20 '12 11:03

LiKao


1 Answers

The specification for that overload of reset is given in 20.7.2.2.4 shared_ptr modifiers [util.smartptr.shared.mod], paragraph 3 (from n3290):

template<class Y> void reset(Y* p);

Effects: Equivalent to shared_ptr(p).swap(*this).

As you can see, that shared_ptr(p) construction creates a new count with new deleter for that p, so nothing good can come off it. It really is the best to think of std::shared_ptr<T>::get as strictly an observer, and using it to deal with lifetime management is a telltale sign that there is something wrong going on.

On the other hand, std::unique_ptr<T> has release, which is exactly what you need when you need to step in and deal with ownership yourself for an instant. Perhaps you can change your design to use std::unique_ptr<T>? It's always possible to create a std::shared_ptr<T> out of it if you need to, eventually. (Although while the std::shared_ptr<T> picks the deleter from the std::unique_ptr<T>, you still need special treatment in the array case as you probably want std::shared_ptr<T*> from std::unique_ptr<T[]>.)

like image 181
Luc Danton Avatar answered Sep 22 '22 07:09

Luc Danton