Many prominent C++ experts promote a widespread usage of smart pointers to a point where they claim that in modern C++, the visible usage of the keyword
new should disappear (well, at least when C++14 will be around to fix the lack of std::make_unique). All dynamic allocations should be hidden by the Standard Library, either with containers like std::vector or with smart pointers.
The standard library's smart pointers can be customized in the way they handle the de-allocation of the memory they hold. This feature is the key to make elegant boundary crossing possible as suggested by this article.
An object is said to cross a dynamic library boundary when it is instantiated within an assembly and consumed by another assembly. A common way this happen is when a factory-like structure instantiate objects and return pointers to them in a dynamic link library.
For example, lets say another library or an executable that links with this library uses the factory to dynamically instantiate and retrieve a pointer to an object. The assembly that consumes the pointer can do anything with it, including a delete on the pointer to free the memory it points to. If the library that allocates the memory and the assembly that consumes the pointer use different versions of the dynamic memory allocation OS run-time library (called the CRT on Windows), there is going to be a problem in the memory allocation book-keeping. For a Microsoft specific example of the problem, see this.
Typically, before the advent of C++11, library developers had to provide functions for de-allocation of objects that were allocated within their library's boundaries, in order to avoid this problem. This had the undesirable side effect that interfaces of such libraries were heavier and required a per-library specific know-how to correctly allocate and de-allocate objects of the library.
An ideal case would be an allocation/de-allocation scheme that the user don't need to know about. He/she just calls the allocation mechanism of the library (e.g. a factory) and don't even bother about de-allocation.
Using the code
The code attached to this article is divided in two projects. It has been done and packaged as a Visual Studio 2012 solution, but it would compile and run fine on g++. You can use Visual Studio Express 2012, freely available from Microsoft. The first project is ExecutableConsumer, which a simple main file that uses a library's factory to instantiate objects from the library. The second project is LibraryFactory, which illustrates a problematic situation and the solution.
The problematic situation is a singleton factory (called ProblematicFactory) that instantiates an object and returns a raw pointer to it. The solution is another singleton factory (called SafeFactory) that instantiates an object and returns a
std::unique_ptr to it, having its custom deleter properly set so that the de-allocation is done in the DLL.
If you run the program in debug mode in Visual Studio, with the macro
USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION defined, you will be able to see that the debugger detects a heap corruption.
Note that the projects provided in the solution are willingly linking with different version of the CRT in order to illustrate the heap corruption problem.
Since code is worth a thousand words, the following sections will be mainly code containing didactic comments from the attached file.
The executable's main file
Note that contexts are created in the main (by using curly braces) to encapsulate individual examples. Remember that at the exit of a context, all local variables are destroyed.
auto wMyObject = ProblematicFactory::getInstance().create();
auto wMyObject = SafeFactory::getInstance().create();
std::shared_ptr< MyClass > wMyObject = SafeFactory::getInstance().create();
The library's problematic factory
This is a typical implementation of a factory that returns a raw pointer to objects it can create.
class LIBRARYFACTORY_API ProblematicFactory
static ProblematicFactory & getInstance();
MyClass * create() const;
The library's safe factory
Syntactically, using this factory is essentially the same as using the problematic one (see in the main file), but it encapsulate the raw pointer in a std::unique_ptr.
class LIBRARYFACTORY_API SafeFactory
static SafeFactory & getInstance();
inline std::unique_ptr< MyClass > create() const
return std::unique_ptr< MyClass >(doCreate());
MyClass * doCreate() const;
The library's object that crosses boundaries
Note that the
default_delete class in this file is a specialization of a std class, so it needs to be in the std namespace.
class LIBRARYFACTORY_API MyClass
class LIBRARYFACTORY_API default_delete< MyClass >
void operator()(MyClass *iToDelete);
Points of Interest
Having code in your projects in the std namespace can seem strange at first, but this is a specialization for your classes. For some std templates, this is totally legal and std::default_delete is a perfect candidate for specialization. Consult this post addressing the question if you are interested.
- v1.0 - 2013-05-20: Initial release.