Reputation: 1838
Say I have an AbstractBaseClass
and a ConcreteSubclass
.
The following code creates the ConcreteSubclass and then disposes of it perfectly fine and without memory leaks:
ConcreteSubclass *test = new ConcreteSubclass(args...);
delete test;
However, as soon as I push this pointer into a vector, I get a memory leak:
std::vector<AbstractBaseClass*> arr;
arr.push_back(new ConcreteSubclass(args...));
delete arr[0];
I get fewer memory leaks with delete arr[0];
than with no delete at all, but still some memory gets leaked.
I did plenty of looking online and this seems to be a well understood problem - I'm deleting the pointer to the memory but not the actual memory itself. So I tried a basic dereference... delete *arr[0];
but that just gives a compiler error. I also tried a whole load of other references and dereferences but each just gives either a compiler error or program crash. I'm just not hitting on the right solution.
Now, I can use a shared_ptr
to get the job done without a leak just fine (Boost as I don't have C++11 available to me):
std::vector<boost::shared_ptr<AbstractBaseClass>> arr2;
arr2.push_back(boost::shared_ptr<AbstractBaseClass>(new ConcreteSubclass(args...)));
but I can't get the manual method to work. It's not really important - it's perfectly easy to just use Boost, but I really want to know what I'm doing wrong so I can learn from it rather than just move without finding out my error.
I tried tracing through Boost's shared_ptr templates, but I keep getting lost because each function has so many overloads and I'm finding it very difficult to follow which branch to take each time.
I think it's this one:
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete
{
boost::detail::sp_pointer_construct( this, p, pn );
}
But I keep ending up at checked_array_delete
, but that can't be right as that uses delete[]
. I need to get down to just
template<class T> inline void checked_delete(T * x)
{
// intentionally complex - simplification causes regressions
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete x;
}
And that's just calling delete
, nothing special. Trying to follow through all of the reference and dereference operators was a disaster from the start. So basically I'm stuck, and I couldn't figure out how Boost made it work whilst my code didn't. So back to the start, how can I re-write this code to make the delete not leak?
std::vector<AbstractBaseClass*> arr;
arr.push_back(new ConcreteSubclass(args...));
delete arr[0];
Thank you.
Upvotes: 0
Views: 180
Reputation: 283624
shared_ptr
stores a "deleter", a helper function1 that casts the pointer back to its original type (ConcreteSubclass*
) before calling delete
. You haven't done this.
If AbstractBaseClass
has a virtual destructor, then calling delete arr[0];
is fine and works just as well as delete (ConcreteSubclass*)arr[0];
. If it doesn't, then deletion through a base subobject is undefined behavior, and can cause far worse things to happen than memory leaks.
Rule of thumb: Every abstract base class should have a user-declared (explicitly defaulted is ok) destructor that is
virtual
OR
protected:
accessibilityor both.
1 You've found the implementation, it is checked_delete
. But it is instantiated with the return type from new
-- checked_delete<ConcreteSubclass>(ConcreteSubclass* x)
. So it is using ordinary delete, but on a pointer to type ConcreteSubclass
, and that makes it possible for the compiler to find the right destructor even without the help of virtual dispatch.
Upvotes: 3