soandos
soandos

Reputation: 5146

Delete an object with a protected destructor

I have to write a shared pointer for class, and among many other things that it has to do is make sure it can delete the object that it is pointing to.

How can I code a solution that will work with an object that has a protected destructor?

Additionally, if the object was created using placement new, I should not be calling delete on the the object, as that space may still be in use (will the delete call even work?). How can I detect such cases?

The relevant bits of the spec:

void reset(); The smart pointer is set to point to the null pointer. The reference count for the currently pointed to object, if any, is decremented.

Sptr(); Constructs a smart pointer that points to the null pointer.

template <typename U> Sptr(U *); Constructs a smart pointer that points to the given object. The reference count is initialized to one.

Sptr(const Sptr &);
template <typename U> Sptr(const Sptr<U> &);

The reference count is incremented. If U * is not implicitly convertible to T *, this will result in a syntax error. Note that both the normal copy constructur and a member template copy constructor must be provided for proper operation.

The way the code is called:

        Sptr<Derived> sp(new Derived);
        char *buf = (char *) ::operator new(sizeof(Sptr<Base1>));
        Sptr<Base1> &sp2 = *(new (buf) Sptr<Base1>());
        sp2 = sp;
        sp2 = sp2;
        sp.reset();
        sp2.reset();
        ::operator delete(buf);

Base1 has everything protected.

Upvotes: 3

Views: 1908

Answers (4)

Yakov Galka
Yakov Galka

Reputation: 72469

Along with the reference counter store a pointer to the function that will delete the object (a 'deleter'). You will instantiate the deleter in the templated constructor of the smart pointer, there you know the derived type. Here is an extremely naive pseudocode:

template<class T> void DefaultDeleter(void *p) { delete static_cast<T*>(p); }

struct ref_counter {
    int refs;
    void *p;
    void (*d)(void *);
};

template<class T> class Sptr { 
    /* ... */
    template <typename U> Sptr(U *p)
    {
        _c = new ref_counter;
        _c->refs = 1;
        _c->p = static_cast<void*>(p);
        _c->d = &DefaultDeleter<U>;
        _p = p;
    }

    T *_p;
    ref_counter *_c;
};

When refs drops to zero, invoke (*_c->d)(_c->p) to destroy the pointed object.

I of course assume that the destructor of Base is protected and the one of Derived is public, as otherwise the exercise makes no sense.

Note: This is why std::shared_ptr can be safely used with base classes with a non-virtual destructor.

Upvotes: 2

Fozi
Fozi

Reputation: 5135

After reading your spec update I can tell you that there is no way of implementing that properly because:

  • There is no way for your pointer to distinguish between the new and placement new case. The only thing your deleter can do is call delete p. That is not the right thing to do in the placement new case although it might work in your example because the buffer happens to be the right size and allocated with new.
  • As explained in the other answers, there is no good way for your deleter to get around the protected destructor problem.

By adding the possibility of a custom deleter to the constructor you would solve both these problems.

Edit: This is how you would add a custom deleter:

template <typename U> Sptr(U *, std::function<void(T*)> &&deleter); Constructs a smart pointer that points to the given object. The reference count is initialized to one. The custom deleter is called when the reference count reaches zero with the raw pointer to the instance.

Upvotes: 0

cHao
cHao

Reputation: 86506

The whole point of making the destructor non-public is to prevent the object from being arbitrarily destroyed. There's no good way to get around that. (Even if there is a general way, it's not a good way, as it would require breaking the hell out of encapsulation in order to do so.)

If you want an object to be destroyed by some class other than itself, make the destructor public. If you don't, then your pointer class won't be able to destroy the object either.

Alternatively, you could make the pointer class a friend of whatever classes you want it to work with. But that's ugly in a number of ways, not least of which is that it rather arbitrarily limits the valid types of objects you can use with it.

Upvotes: 4

NPE
NPE

Reputation: 500237

Your class could take a deleter functor that would then become responsible for deallocating the object. By doing this you'd dump the problem of access to the destructor onto whoever's using your class. :)

Joking aside, if the caller knows how to create instances of the class, they should also know how to destroy those instances.

This would also provide a way to solve the problems associated with placement new.

Upvotes: 0

Related Questions