3

Say we have a base class and a derived. So:

class base {
     protected:
          ~base(){
                //...
          }
     // ...
};

class derived : public base {
     // ...
};

And now say that we have this code using the above classes with a smart pointer class:

SmartPointer<base> bptr(new derived());
delete bptr;

I understand that it would prevent slicing of the derived object by calling the destructor of derived, but how does it know to do that? Wouldn't the reference stored in the smart pointer be that of type base*? Does it traverse some kind of hierarchy tree, cast that pointer to derived* and then call delete? Or is there some other thing that I don't know about?

The implementation is supposedly threadsafe, non-intrusive, and reference counting.

YES, the classes that you see are akin to the ones that I'm testing against. There is apparently a way to do this with THESE GIVEN classes. The main idea as to how is mentioned in my question above, but I'm not sure as to how one such an implementation would work.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
K-RAN
  • 896
  • 1
  • 13
  • 26
  • 1
    The above code won't compile, as the destructor of `base` is private, aka non-accessible. – Xeo Apr 26 '11 at 07:04
  • 2
    Which `SmartPointer` implementation are you using? – CB Bailey Apr 26 '11 at 07:07
  • @Xeo: I know that it doesn't compile. However I have seen an implementation that does compile and runs correctly, but I have not seen the actual code. @Charles Bailey: It's supposed to be a reference counting implementation. – K-RAN Apr 26 '11 at 07:12
  • 1
    Make sure the destructor of any base class is `virtual`! – xtofl Apr 26 '11 at 07:14
  • Say that I was designing the smart pointer to fit the criteria in the edit above, and that someone else gave me those classes to work with...any idea as to what a good reference or direction would be? – K-RAN Apr 26 '11 at 07:18

5 Answers5

7

First thing is that as it stands the code will not work. The destructor of base must be at the very least protected (or derived classes be friends of the base). A private destructor means that the compiler will not allow you to write the destructor for the derived classes. Now, assuming that you have a protected destructor... (Rembember, if you design a class to be extended, provide either a public virtual destructor or a protected non-virtual!)

All depends on the implementation of the SmartPointer, in particular std::shared_ptr (or the boost counterpart boost::shared_ptr) are able to manage that situation cleanly. The solution performs some sort of partial type erasure of the type for destruction purposes. Basically, the smart pointer has a templated constructor that accepts any pointer that can be assigned to a base pointer, but because it is templated it knows the concrete type. At that point it stores a synthetic deleter function that will call the appropriate destructor.

For simplicity, using std::function:

template <typename T>
void delete_deleter( void * p ) {
   delete static_cast<T*>(p);
}

template <typename T>
class shared_pointer {
    T * ptr;
    std::function<void(void*)> deleter;
public:
    template <typename U>
    shared_pointer( U* p, std::function<void()> d = delete_deleter<U> ) 
       : ptr(p), deleter(d)
    {}
    ~shared_pointer() {
       deleter( ptr );  // call the stored destructor
    }
};

The code is for exhibition only, it would have to be tweaked for production (where to store the function, reference counting...), but it is enough to give you the idea: in the only function where the exact type of the object is known (when creating the smart pointer), you create a wrapper that will call the exact version of the destructor that you need (providing some short of type erasure), then just leave it around and when you need to delete the object call it instead of the delete operator.

This can also be used to manage other resources that require calling a special method instead of delete:

// exhibition only!
shared_pointer<Foo> p( Factory.create(), &Factory::release );

Again there should be quite a lot of work before making this production ready.

Dependency on std::function which is used to simplify the erasure, can be eliminated from the problem. In the simple case (only memory allocated with new and freed with delete is supported in the smart pointer), then just provide a deleter base class with a single virtual operator()(void*), and then refactor the existing delete_deleter into templated derived classes from deleter that override operator()(void*) with the current implementation. If you need to go for the general case (hold any type of resource) it is not worth the effort, just use std::function or boost::function.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    +1, but to be picky `~base` doesn't *need* to be `protected` if `derived` is a `friend` of `base`. – dalle Apr 26 '11 at 08:00
  • 1
    @dalle: the problem with making `friend`s that way is scalability, you will need to modify your base class for each and every new type. Also you are increasing coupling (friendship is a stronger coupling relationship than inheritance, it is the *most* coupling relationship) by allowing access to *all* of `base` private parts. I would recommend against friendship in this scenario *always*, unless there is a strong reason (say that you want to allow only a limited set of derived classes, then the maintainability problem is actually an advantage!) – David Rodríguez - dribeas Apr 26 '11 at 08:05
  • íguez-dribeas: I mostly objected to the word **must** in the phrase "The destructor of base must be at the very least protected". I agree with what you say, but in the OP case the use of private may have it's use, as you also describe. – dalle Apr 26 '11 at 09:27
2

Well first of all, your destructor shouldn't be private or that won't compile at all. Secondly, if you're using a "smart pointer", you probably should not be deleting the pointer by hand at all (I don't know what implementation you're using though, but this strikes as odd to me).

Anyways if you're curious how the derived class' destructor gets called when the object is deleted through a pointer to the base class, the answer is polymorphism. But you're missing virtual declaration from your destructor, right now your code would not call the derived class' destructor.

How most C++ implementations implement this is through a virtual table.

reko_t
  • 55,302
  • 10
  • 87
  • 77
1

If you using any of boost smart pointers or some other which is not friend of your Base class, then this code wouldn't compile, because destructor of Base class is protected (which is same as private for other independent from Base classes).

Now let's consider that you make SmartPointer<Base> friend of Base. This case the code will work, but it wouldn't call destructor of Derived but destructor of Base, because here your Base class is not polymorphic. You should declare destrucotr of Base as virtual. In last case the correct destructor will be called when your smart pointer is deleted.

Mihran Hovsepyan
  • 10,810
  • 14
  • 61
  • 111
  • It should work for classes like this. Also, it is must not be intrusive. I forgot to add that..I'll do so in an edit. – K-RAN Apr 26 '11 at 07:14
  • How should it work if destructor of your base class is protected, and how should it call destructor of `Derived` if it is non virtual? – Mihran Hovsepyan Apr 26 '11 at 07:20
  • That is actually the heart of my question. I'll add that. – K-RAN Apr 26 '11 at 07:23
0

this program is invalid.

1) the dtor of base is private

2) the dtor of base is not virtual

to answer your question: you need to correct #1 and #2. then the dtor will be called using dynamic dispatch (which will invoke each dtor in reverse order of construction).

without making those corrections, the only way SmartPointer could know to call derived's dtor in this example, and in a defined manner, is if SmartPointer was overly clever (or tedious to use).

justin
  • 104,054
  • 14
  • 179
  • 226
  • 3
    "overly clever" as `boost::shared_ptr`? – dalle Apr 26 '11 at 07:16
  • Yes. It is overly clever (I have seen an implementation that works, but I have no idea as to what the code looks like!) – K-RAN Apr 26 '11 at 07:19
  • @dalle since `boost::shared_ptr bptr(new derived());` is a malformed program, i've failed to see your point. – justin Apr 26 '11 at 07:31
  • @Justin: `boost::shared_ptr bptr(new derived());` is 100% correct. It stores a correct deleter internally. – Yakov Galka Apr 26 '11 at 07:49
  • @Justin: Regardless if `~base` is `virtual` or not, `boost::shared_ptr bptr(new derived());` is well defined and valid. Even `boost::shared_ptr bptr(new derived());` is well defined and valid. Your first point still need to be addressed somehow, so that `derived` can access `~base`. – dalle Apr 26 '11 at 07:50
  • @Justin: It is perfectly valid a well-defined to write `boost::shared_ptr bptr(new derived());`. That's why `boost::dynamic_pointer_cast<>` exists. What are you trying to say ? – ereOn Apr 26 '11 at 07:52
  • @Justin: I have added a sketch of how that can be safely implemented [here](http://stackoverflow.com/questions/5787033/question-on-smart-pointers-in-c/5787480#5787480) – David Rodríguez - dribeas Apr 26 '11 at 07:57
0

Your base class desctructor needs to be virtual to ensure that destructor of derived class is called when deleting via base pointer.

Wikipedia entry on virtual desctructors

kauppi
  • 16,966
  • 4
  • 28
  • 19