Section I: What is the purpose?
The checked delete idiom adds a compile-time safety check against deleting incomplete classes. The C++ Standard mandates that a pointer to an incomplete class may be deleted, so long as the class has a
trivial desctructor and
does not overload operator delete. Deleting pointers to classes with non-trivial destructors, or classes that overload operator delete results in
undefined behavior.
Section II: Problematic Code Example
Consider the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
/* Deleter.hpp */
#include<cstdlib>
class A;
class B;
class C;
void func1(A* a);
void func2(B* b);
void func3(C* c);
/* End of Deleter.hpp */
/* Deleter.cpp */
#include "Deleter.hpp"
void func1(A* a)
{
delete a;
}
void func2(B* b)
{
delete b;
}
void func3(C* c)
{
delete c;
}
/* End of Deleter.cpp */
/* Classes.hpp */
class A
{
private:
int* x;
public:
A(void)
{
x = new int(4);
}
~A(void)
{
delete x;
}
};
class B { };
class C
{
public:
void operator delete(void* pointer)
{
free(pointer);
}
};
/* End of Classes.hpp */
/* Main.cpp */
#include "Deleter.hpp"
#include "Classes.hpp"
int main(void)
{
A* a = new A;
func1(a); // <-- Undefined Behavior - A has a non-trivial destructor
B* b = new B;
func2(b); // <-- Legal - B has a trivial destructor and does not overload operator delete
C* c = new C;
func3(c); // <-- Undefined Behavior - C has an overloaded operator delete
return 0;
}
/* End of Main.cpp */
|
Most compilers generate a warning when this occurs, however some compilers have been known not to catch these errors. This is what the checked delete idiom is designed to remedy.
Section III: Line-By-Line Evaluation
The original source is listed at the top of this article.
The following code has been stripped of comments and preprocessor directives.
Boost's implementation of the checked delete idiom consists of two function templates and two class templates.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
template<class T> void checked_delete(T* p)
{
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete p;
}
template<class T> struct checked_deleter
{
typedef void result_type;
typedef T * argument_type;
void operator()(T * x) const
{
boost::checked_delete(x);
}
};
template<class T> void checked_array_delete(T* p)
{
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete [] p;
}
template<class T> struct checked_array_deleter
{
typedef void result_type;
typedef T * argument_type;
void operator()(T * x) const
{
boost::checked_array_delete(x);
}
};
|
While the bodies of the code look very similar, one set is used to check
operator delete
and the other is used to check
operator delete[]
.
Warning - The following contains opinions that cannot be cited to any documentation from boost.
Lines 1-6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
template<class T> void checked_delete(T* p)
{
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete p;
}
// Line 3:
// sizeof(T) will either evaluate to 0,
// or cause a compiler error if T is an incomplete type.
// If sizeof(T) evaluates to 0, then a compiler error is
// caused by attempting to declare an array of -1 elements.
//
// Line 4: * Citation Needed
// While I cannot be sure of why line 4 is there,
// I assume that it is to prevent the compiler from
// complaining about an unused variable, while at the
// same time helping it optimize the build casting the
// result of sizeof(type_must_be_completed) to void.
//
// Line 5:
// operator delete is called on the parameter
//
// Furthermore, it can be deduced that the defined
// type, `type_must_be_complete`, is named as such
// to aid in the debugging process, as it will show
// up in the compiler output as an indicator of where
// the problem lies.
|
Lines 8-16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
template<class T> struct checked_deleter
{
typedef void result_type;
typedef T * argument_type;
void operator()(T * x) const
{
boost::checked_delete(x);
}
};
// Lines 3 & 4:
// As far as I can see, lines 3 and 4 are present
// for documentation purposes.
// Lines 5-8:
// The overloaded operator() simply calls the corresponding
// function.
|
Lines 19-34:
The line-by-line for checked_array_delete is essentially the same as above.