Reputation: 73
Short: deleting a templatized pointer does not call the destructor.
I just ran into a situation that I can't explain. I try to break the more complex situation down here.
File R.cpp
class R {
Owner<Problem> m_o;
void cleanUp() { m_o.clear(); }
}
File Owner.cpp
struct DeleteFunctor {
template< class TPtr > void operator()(TPtr* ptr) { delete ptr; }
};
template< class T >
class Owner {
std::vector<T*> m_objects;
// here add and other stuff
void clear() {
std::for_each( m_objects.begin(), m_objects.end(), DeleteFunctor() );
m_objects.clear();
}
I now have a utility class that creates new Problem-objects on the heap and inserts them into m_o directly. I know that it's bad style to export references to internal types but that's not the point.
If I call cleanUp() I can trace it down up to the call delete ptr in the Functor, with ptr having the correct Problem-type. But the Problem-destructor is not called!!
However, including the Problem header in file R.cpp fixes the problem. The comiler does not complain. Is that a compiler bug?
System: g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Upvotes: 4
Views: 503
Reputation: 238321
From the standard (draft n3242) §5.3.5/5:
If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
If you don't include the header which defines T
or Problem
in this case, then you're deleting an object of incomplete type. If that type does not fulfill the requirements in that clause, then the deletion has undefined behaviour.
Is that a compiler bug?
No, compilers are not required to warn about undefined behaviour.
You may want to require T
of Owner
to be complete. Something like static_assert(sizeof(T) > 0)
should fail to compile if T
is incomplete.
If you can't rely on the current standard, you can instead use boost::checked_deleter
as your delete functor. It does the completeness check in pre c++11. If you for some reason don't want to include a boost header, then just reimplement it. This is the code which checks for completeness.
// intentionally complex - simplification causes regressions
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete x;
Note that you're effectively partially re-implementing std::vector<std::unique_ptr<T>>
If you can use c++11, I recommend to use that instead.
Upvotes: 5