63900

Nonvirtual deleter with smart pointers

Question:

I was reading the latest Overload (<a href="http://accu.org/var/uploads/journals/Overload120.pdf" rel="nofollow">link</a>) and decided to test out the statement at page 8:

<blockquote>

shared_ptr will properly invoke B’s destructor on scope exit, even though the destructor of A is not virtual.

</blockquote>

I am using Visual Studio 2013, compiler v120:

#include <memory> #include <iostream> struct A { ~A() { std::cout << "Deleting A"; } }; struct B : public A { ~B() { std::cout << "Deleting B"; } }; int main() { std::shared_ptr<A> ptr = std::make_shared<B>(); ptr.reset(); return 0; }

This works as expected and prints out "Deleting BDeleting A"

The article seems to imply that this should also work with std::unique_ptr:

<blockquote>

There is almost no need to manage your own resources so resist the temptation to implement your own copy/assign/move construct/move assign/destructor functions.

Managed resources can be resources inside your class definition or instances of your classes themselves. Refactoring the code around standard containers and class templates like unique_ptr or shared_ptr will make your code more readable and maintainable.

</blockquote>

However, when changing

std::shared_ptr<A> ptr = std::make_shared<B>();

to

std::unique_ptr<A> ptr = std::make_unique<B>();

the program will only output "Deleting A"

Did I misunderstand the article and the behavior is intended by the standard? Is it a bug with the MSVC compiler?

Answer1:

shared_ptr and unique_ptr are different. make_shared will create a type erased deleter object on invocation while with unique_ptr the deleter is part of the type. Therefore the shared_ptr knows the real type when it invokes the deleter but the unique_ptr doesn't. This makes unique_ptr's much more efficient which is why it was implemented this way.

I find the article a bit misleading actually. I do not consider it to be good advice to expose copy constructors of a base class with virtual functions, sounds like a lot of slicing problems to me.

Consider the following situation:

struct A{ virtual void foo(){ std::cout << "base"; }; }; struct B : A{ virtual void foo(){ std::cout << "derived"; }; }; void bar(A& a){ a.foo(); //derived auto localA = a; //poor matanance porgrammer didn't notice that a is polymorphic localA.foo(); //base }

I would personally advocate non-intrusive polymorphism <a href="http://isocpp.org/blog/2012/12/value-semantics-and-concepts-based-polymorphism-sean-parent" rel="nofollow">http://isocpp.org/blog/2012/12/value-semantics-and-concepts-based-polymorphism-sean-parent</a> for any new higherarchies, it sidesteps the problem entirely.

Answer2:

This behaviour is according to the standard.

The unique_ptr is designed to have no performance overhead compared to the ordinary new/delete.

The shared_ptr has the allowance to have the overhead, so it can be smarter.

According to the standard [20.7.1.2.2, 20.7.2.2.2], the unique_ptr calls delete on the pointer returned by get(), while the shared_ptr deletes the real object it holds - it remembers the proper type to delete (if properly initialized), even without a virtual destructor.

Obviously, the shared_ptr is not all-knowing, you can trick it to behave badly by passing a pointer to the base object like this:

std::shared_ptr<Base> c = std::shared_ptr<Base> { (Base*) new Child() };

But, that would be a silly thing to do anyhow.

Answer3:

You <em>could</em> do something similar with unique_ptr, but since its deleter type is determined statically, you need to statically maintain the proper deleter type. i.e. (<a href="http://coliru.stacked-crooked.com/a/af5f44996ca3b1c2" rel="nofollow">Live demo at Coliru</a>):

// Convert given pointer type to T and delete. template <typename T> struct deleter { template <typename U> void operator () (U* ptr) const { if (ptr) { delete static_cast<T*>(ptr); } } }; // Create a unique_ptr with statically encoded deleter type. template <typename T, typename...Args> inline std::unique_ptr<T, deleter<T>> make_unique_with_deleter(Args&&...args) { return std::unique_ptr<T, deleter<T>>{ new T(std::forward<Args>(args)...) }; } // Convert a unique_ptr with statically encoded deleter to // a pointer to different type while maintaining the // statically encoded deleter. template <typename T, typename U, typename W> inline std::unique_ptr<T, deleter<W>> unique_with_deleter_cast(std::unique_ptr<U, deleter<W>> ptr) { T* t_ptr{ptr.release()}; return std::unique_ptr<T, deleter<W>>{t_ptr}; } // Create a unique_ptr to T with statically encoded // deleter for type U. template <typename T, typename U, typename...Args> inline std::unique_ptr<T, deleter<U>> make_unique_with_deleter(Args&&...args) { return unique_with_deleter_cast<T>( make_unique_with_deleter<U>(std::forward<Args>(args)...) ); }

It's a bit awkward to use:

std::unique_ptr<A, deleter<B>> foo = make_unique_with_deleter<A, B>();

auto improves the situation:

auto bar = make_unique_with_deleter<A, B>();

It doesn't really buy you much, since the dynamic type is encoded right there in the static type of the unique_ptr. If you're going to carry the dynamic type around, why not simply use unique_ptr<dynamic_type>? I conjecture such a thing may have some use in generic code, but finding an example of such is left as an exercise to the reader.

Answer4:

The technique std::shared_ptr employs to do the magic is called <a href="http://www.cplusplus.com/articles/oz18T05o/" rel="nofollow">type erasure</a>. If you are using gcc, try to find the file bits/shared_ptr_base.h and check the implementation. I'm using gcc 4.7.2.

unique_ptr is designed for minimum overhead and does not use type erasure to remember the actual type of the pointer it holds.

Here is a great discussion on this topic: <a href="https://stackoverflow.com/a/22861890/930095" rel="nofollow">link</a>

<hr />

EDIT: a simple implementation of shared_ptr to showcase how type erasure is achieved.

#include <cstddef> // A base class which does not know the type of the pointer tracking class ref_counter_base { public: ref_counter_base() : counter_(1) {} virtual ~ref_counter_base() {} void increase() { ++counter_; } void release() { if (--counter_ == 0) { destroy(); delete this; } } virtual void destroy() = 0; private: std::size_t counter_; }; // The derived class that actually remembers the type of // the pointer and deletes the pointer on destroy. template <typename T> class ref_counter : public ref_counter_base { public: ref_counter(T *p) : p_(p) {} virtual void destroy() { delete p_; } private: T *p_; }; template <typename T> class shared_ptr { public: shared_ptr(T *p) : p_(p) , counter_(new ref_counter<T>(p)) { } // Y* should be implicitely convertable to T*, // i.e. Y is derived from T template <typename Y> shared_ptr(Y &other) : p_(other.get()) , counter_(other.counter()) { counter_->increase(); } ~shared_ptr() { counter_->release(); } T* get() { return p_; } ref_counter_base* counter() { return counter_; } private: T *p_; ref_counter_base *counter_; };

Recommend

  • why my code run wrong ,it is about '@property'
  • Virtual StringTree's hint windows are left out on the screen
  • Questions About Running time
  • For which standard container, if any, is the iterator returned by end() persistent?
  • How to change object child members in a set
  • Making a cross platform library with CMake?
  • The method Distinct is not supported
  • how to correctly return std::list from dll
  • SiteMesh: Changing the content-type of the response
  • java.lang.NoClassDefFoundError: Could not initialize class sun.nio.ch.FileChannelImpl
  • QSystemTrayIcon and Windows8
  • How to get to older Xcode beta version?
  • Safari PHP form submission -file upload hangs
  • Gforce min not supported for character in data.table
  • Ruby: Why does this way of using map throw an error?
  • How to unwind to the first view controller on a navigation stack
  • Is it safe to accept URL parameters for populating the `url_for` method?
  • OpenCV Python: Draw minAreaRect ( RotatedRect not implemented)
  • Can a Collections.shuffle be considered equivalent to a series of Randoms?
  • Nested projects in multiproject visual studio templates
  • Is there a way to call library thread-local init/cleanup on thread creation/destruction?
  • Why can't I use non-integral types with switch [duplicate]
  • Why can't UI components be accessed from a backgroundworker?
  • How can I prevent the need to copy strings passed to a avr-gcc C++ constructor?
  • Accessing Rows In A LINQ Result Without A Foreach Loop?
  • In C what exactly happens if i use () to initialize a double dimension array instead of the {}?
  • Casting between Interfaces and Classes
  • C++ pointer value changes with static_cast
  • Marklogic : Query response time is very high
  • RectangularRangeIndicator format like triangular using dojo
  • Javascript convert timezone issue
  • python regex in pyparsing
  • Error creating VM instance in Google Compute Engine
  • How can I get HTML syntax highlighting in my editor for CakePHP?
  • Suggestions to manage Login/Logout transitions
  • Hits per day in Google Big Query
  • how does django model after text[] in postgresql [duplicate]
  • How do I configure my settings file to work with unit tests?
  • IndexOutOfRangeException on multidimensional array despite using GetLength check
  • Binding checkboxes to object values in AngularJs