tags:

views:

381

answers:

1

I've been teaching myself the smart pointers that are part of C++0x and came across something that feels inconsistent to me. Specifically, how the destruction policy of unique_ptr<> and shared_ptr<> are handled.

For unique_ptr<>, you can specialize std::default_delete<> and from then on unless you explicitly request a different destruction policy, the new default will be used.

Consider the following:

struct some_c_type;

some_c_type *construct_some_c_type();
void destruct_some_c_type(some_c_type *);

namespace std  {
    template <> struct default_delete<some_c_type> {
        void operator()(some_c_type *ptr) {
            destruct_some_c_type(ptr);
        }
     };
}

Now, once that's in place, unique_ptr<> will use the appropriate destruction policy by default:

// Because of the specialization, this will use destruct_some_c_type
std::unique_ptr<some_c_type> var(construct_some_c_type());

Now compare this to shared_ptr<>. With shared_ptr<>, you need to explicitly request the appropriate destruction policy or it defaults to using operator delete:

// error, will use operator delete 
std::shared_ptr<some_c_type> var(construct_some_c_type());

// correct, must explicitly request the destruction policy
std::shared_ptr<some_c_type> var(construct_some_c_type(),
                                 std::default_delete<some_c_type>());

Two questions.

  1. Am I correct that shared_ptr<> requires the destruction policy to be specified every time it's used or am I missing something?
  2. If I'm not missing something, any idea why the two are different?

P.S. The reason I care about this is my company does a lot of mixed C and C++ programming. The C++ code often needs to use C-style objects so the ease of specifying a different default destruction policy is quite important to me.

+3  A: 

I think the question boils down to why std::shared_ptr can have no associated deleter (in which case it just calls delete) rather than constructing a std::default_delete by default. (No idea. If the intention was that default_delete is for specializing, one would expect it to be used by shared_ptr.)

Otherwise there are trade-offs.

It is better to have fewer template arguments. Boost's reference mentions that this allows a factory to change the allocation scheme without the change affecting the user of the factory.

A unique_ptr on the other hand is supposed to be very light-weight. How would you store the deleter with zero space overhead (in case of a functor without members) if it wasn't part of the type (GCC uses tuple, where memberless objects don't take memory space)?


Subjectively, I think I'd prefer:

unique_ptr<FILE, FCloser> f(fopen(x, y));

to

unique_ptr<FILE> f(fopen(x, y)); //default_delete<FILE> has been specialized

In the first case there's nothing to guess. If the resource does not come from new or new[], a deleter has to be explicitly given.

UncleBens
I agree: Explicit is better than implicit.
dalle
@UncleBens - one issue with the implicit/explicit discussion is the standard specifically allows allows for implicit which will always compile but could cause undefined behavior. If you always had to specify the deleter even for a simple `delete` or `delete []` that may be okay. But as it is, it's easy to forget, the build will succeed and such a bug could be easy to miss.
R Samuel Klatchko
Specialize it for FILE and the like to static_assert? :) But even if you specialize it, you'll need to remember to include the header with the specializations?
UncleBens
@UncleBens - for types like FILE, you do need to include the header file. But for local C types, we can include the specialization in the same file that declares the C type's constructor/destructor.
R Samuel Klatchko