views:

1173

answers:

2

Do you have to pass delete the same pointer that was returned by new, or can you pass it a pointer to one of the classes base types? For example:

class Base
{
public:
    virtual ~Base();
    ...
};

class IFoo
{
public:
    virtual ~IFoo() {}
    virtual void DoSomething() = 0;
};

class Bar : public Base, public IFoo
{
public:
    virtual ~Bar();
    void DoSomething();
    ...
};

Bar * pBar = new Bar;
IFoo * pFoo = pBar;
delete pFoo;

Of course this is greatly simplified. What I really want to do is create a container full of boost::shared_ptr and pass it to some code that will remove it from the container when it is finished. This code will know nothing of the implementation of Bar or Base, and will rely on the implied delete operator in the shared_ptr destructor to do the right thing.

Can this possibly work? My intuition says no, since the pointers will not have the same address. On the other hand, a dynamic_cast<Bar*> should work, so somewhere the compiler is storing enough information to figure it out.


Thanks for the help, everybody who answered and commented. I already knew the importance of virtual destructors, as shown in my example; after seeing the answer I gave it a little thought, and realized the whole reason for a virtual destructor is this exact scenario. Thus it had to work. I was thrown by the absence of a visible means of converting the pointer back to the original. A little more thinking led me to believe there was an invisible means, and I theorized that the destructor was returning the true pointer for delete to release. Investigating the compiled code from Microsoft VC++ confirmed my suspicion when I saw this line in ~Base:

mov eax, DWORD PTR _this$[ebp]

Tracing the assembler revealed that this was the pointer being passed to the delete function. Mystery solved.

I've fixed the example to add the virtual destructor to IFoo, it was a simple oversight. Thanks again to everyone who pointed it out.

+14  A: 

Yes, it will work, if and only if the base class destructor is virtual, which you have done for the Base base class but not for the IFoo base class. If the base class destructor is virtual, then when you call operator delete on the base class pointer, it uses dynamic dispatch to figure out how to delete the object by looking up the derived class destructor in the virtual function table.

In your case of multiple inheritance, it will only work if the base class you're deleting it through has a virtual destructor; it's ok for the other base classes to not have a virtual destructor, but only if you don't try to delete any derived objects via those other base class pointers.

Adam Rosenfield
I know the importance of a virtual destructor to destroy the object properly, which is why I included it in the example; you're saying it will also cause the proper memory to get freed? And I need a virtual destructor for IFoo too?
Mark Ransom
Actually it's just fine to delete any reference as long as the base most class has a virtual destructor. Once a destructor is virtual, all of the derived implementations will have a virtual destructor whether or not it's specified. C++ is fun.
JaredPar
The actual memory will likely be freed even without the virtual destructor.
Eclipse
The problem is that you didn't include a virtual destructor where you need it. You included a virtual destructor in Base but you didn't in IFoo. Your delete statement calls the non-virtual destructor in IFoo, so it doesn't filter up to Bar, doesn't filter back down to Base, and leaks memory.
Windows programmer
@Windows Programmer: entirely wrong, the virtual property of function is inherited. So if a function is virtual, and overloads found in derived classes are virtual even without the keyword. JaredPar has it correct.
Evan Teran
I am afraid JaredPar and Evan did not look closely enough at the example.
Gorpik
To be specific: Bar's destructor is "magically" made virtual (except it doesn't need to be, since it's declared virtual anyway), but IFoo's destructor is not, and that's the one that gets called in the code in the question.
Steve Jessop
If you want to survive in this industry, you'd better not read the actual code, you'd better give an answer based on imaginary code.
Windows programmer
+1  A: 

This doesn't relate to your given example, but since you mentioned that you're really interested in shared_ptr's behavior when deleting its owned object, you might be interested in using shared_ptr's 'deleter'.

If the object owned by the shared_ptr needs special handling when being deleted, you can specify a 'deleter' for any particular shared_ptr<>. The deleter is not part of the type, it's an attribute of a shared_ptr<> instance, so your container of shared_ptr<> objects could have some objects with different deleters. Here's what the Boost docs say about the shared_ptr<> deleter:

Custom deallocators allow a factory function returning a shared_ptr to insulate the user from its memory allocation strategy. Since the deallocator is not part of the type, changing the allocation strategy does not break source or binary compatibility, and does not require a client recompilation. For example, a "no-op" deallocator is useful when returning a shared_ptr to a statically allocated object, and other variations allow a shared_ptr to be used as a wrapper for another smart pointer, easing interoperability.

It would be cleanest if you could modify IFoo to have a virtual destructor since you're planning to delete objects that are subclasses of it through an IFoo reference or pointer. But if you're stuck with an IFoo that cannot be corrected, then if you want to use shared_ptr<IFoo> in your container, but have it pointing to a Bar, you could create the shared_ptr instance with a deleter that performs a downcast to a Bar* then does the delete operation. Downcasts are considered bad form, but this technique might be something that you could use in a bind.

Michael Burr