Beyond the C++ Standard Library: An Introduction to Boost

Header: "boost/checked_delete.hpp"

When deleting an object through a pointer, the result is typically dependent on whether the type being deleted is known at the time of the deletion. There are hardly ever compiler warnings when delete-ing a pointer to an incomplete type, but it can cause all kinds of trouble, because the destructor may not be invoked. This, in turn, means that cleanup code won't be performed. checked_delete is in effect a static assertion that the class type is known upon destruction, enforcing the constraint that the destructor will be called.

Usage

checked_delete is a template function residing in the boost namespace. It is used for deleting dynamically allocated objectsand there's a companion used for dynamically allocated arrays called checked_array_delete. The functions accept one argument; the pointer or array to be deleted. Both of these functions require that the types they delete be known at the time they are destroyed (that is, when they are passed to the functions). To use the functions, include the header "boost/checked_delete.hpp". When utilizing the functions, simply call them where you would otherwise call delete. The following program forward declares a class, some_class, that is never defined. Any compiler would allow a pointer to some_class to be deleted (more on this later), but checked_delete does not compile until a definition of some_class is available.

#include "boost/checked_delete.hpp" class some_class; some_class* create() { return (some_class*)0; } int main() { some_class* p=create(); boost::checked_delete(p2); }

When trying to compile this program, the instantiation of the function checked_delete<some_class> fails because some_class is an incomplete type. Your compiler will say something like this:

checked_delete.hpp: In function 'void boost::checked_delete(T*) [with T = some_class]': checked_sample.cpp:11: instantiated from here boost/checked_delete.hpp:34: error: invalid application of 'sizeof' to an incomplete type boost/checked_delete.hpp:34: error: creating array with size zero ('-1') boost/checked_delete.hpp:35: error: invalid application of 'sizeof' to an incomplete type boost/checked_delete.hpp:35: error: creating array with size zero ('-1') boost/checked_delete.hpp:32: warning: 'x' has incomplete type

The first part of the preceding error message clearly spells out the problem: that checked_delete has encountered an incomplete type. But when and how are incomplete types problems in our code? The following section talks about exactly that.

What's the Problem, Anyway?

Before we really start enjoying the benefits of checked_delete, let's make sure that we understand the problem in full. If you try to delete a pointer to an incomplete type[3] with a non-trivial destructor,[4] the result is undefined behavior. How can that come about? Let's look at an example.

[3] An incomplete type is one that has been declared but not defined.

[4] That's Standardese for saying that the class, one or more of its direct bases, or one or more of its non-static data members has a user-defined destructor.

// deleter.h class to_be_deleted; class deleter { public: void delete_it(to_be_deleted* p); }; // deleter.cpp #include "deleter.h" void deleter::delete_it(to_be_deleted* p) { delete p; } // to_be_deleted.h

#include <iostream> class to_be_deleted { public: ~to_be_deleted() { std::cout << "I'd like to say important things here, please."; } }; // Test application #include "deleter.h" #include "to_be_deleted.h" int main() { to_be_deleted* p=new to_be_deleted; deleter d; d.delete_it(p); }

The preceding code tries to delete a pointer to an incomplete type, to_be_deleted, resulting in undefined behavior. Notice that to_be_deleted is forward declared in deleter.h; that deleter.cpp includes deleter.h and not to_be_deleted.h: and that to_be_deleted.h defines a non-trivial destructor for to_be_deleted. It can be easy to get into this kind of trouble, especially when using smart pointers. What we need is a way to ensure that a type is complete when calling delete, and that's just what checked_delete does.

checked_delete to the Rescue

The previous example shows that it's feasible to get into trouble when deleting incomplete types without realizing it, and not all compilers even emit a warning when it happens. When writing generic code, avoiding that situation is imperative. To rewrite the example to make use of checked_delete, you just need to change the delete p to checked_delete(p).

void deleter::do_it(to_be_deleted* p) { boost::checked_delete(p); }

checked_delete is basically a static assertion that the class type is complete, which is accomplished like so:

template< typename T > inline void checked_delete(T * x) { typedef char type_must_be_complete[sizeof(T)]; delete x; }

The idea here is to create an array of char, with the number of array elements being equal to the size of T. If checked_delete is instantiated with a type T that is incomplete, the compilation fails, because sizeof(T) returns 0, and it's illegal to create an (automatic) array with 0 elements. You could also have used BOOST_STATIC_ASSERT for asserting this.

BOOST_STATIC_ASSERT(sizeof(T));

This utility is very handy when writing templates that must ensure that they are instantiated only with complete types. There is also a corresponding "checked deleter" for arrays, called checked_array_delete, which works just like checked_delete.

to_be_deleted* p=new to_be_deleted[10]; boost::checked_array_delete(p);

Summary

When a dynamically allocated object is deleted, it is imperative that its destructor is called. If the type is incompletethat is, it has been declared but not definedthe destructor will probably never be called. This is a potentially disastrous situation, so avoiding it is paramount. For class templates and functions, the risk is greater than for other types, because there's no telling in advance which types will be used with it. When using checked_delete and checked_array_delete, the problem of deleting incomplete types is removed. There is no runtime overhead compared to a direct call to delete, so the extra safety brought forth by checked_delete comes virtually without a price.

Use checked_delete when you need to ensure that types are complete when calling delete.

    Категории