Ensuring that objects allocated in one dynamic library are deleted by the same library has always been a challenge. Solutions prior to the advent of C++11 often make the usage of the library cumbersome. This is a solution using smart pointers' custom deleters.
Introduction
Many prominent C++ experts promote the usage of smart pointers instead of raw pointers to a point where they claim that in modern C++, the visible usage of the keyword new
should disappear. 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.
Background
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 happens is when a factory-like structure instantiates objects and returns pointers to them in a dynamic link library.
For example, let's 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 doesn't need to know about. They just call the allocation mechanism of the library (e.g., a factory) and don't even bother about de-allocation.
Using the Code
The code related to this article is divided in two projects. The first project is ExecutableConsumer
, which is 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 factory (called ProblematicFactory
) that instantiates an object and returns a raw pointer to it. The solution is another 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.
#include <ProblematicFactory.h>
#include <SafeFactory.h>
#undef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION
int main()
{
#ifdef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION
{
auto wMyObject = ProblematicFactory::create();
delete wMyObject;
}
#endif
{
auto wMyObject = SafeFactory::create();
}
{
std::shared_ptr< MyClass > wMyObject = SafeFactory::create();
}
return 0;
}
The Library's Problematic Factory
This is a typical implementation of a factory that returns a raw pointer to objects it can create.
#pragma once
#include "DllSwitch.h"
#include "MyClass.h"
class ProblematicFactory
{
public:
static LIBRARYFACTORY_API MyClass * create();
private:
ProblematicFactory();
};
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 encapsulates the raw pointer in a std::unique_ptr
.
#pragma once
#include "DllSwitch.h"
#include "MyClass.h"
#include <memory>
class SafeFactory
{
public:
inline static std::unique_ptr< MyClass > create()
{
return std::unique_ptr< MyClass >(doCreate());
}
private:
SafeFactory();
static LIBRARYFACTORY_API MyClass * doCreate();
};
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.
#pragma once
#include "DllSwitch.h"
#include <memory>
class LIBRARYFACTORY_API MyClass
{
};
template<>
class LIBRARYFACTORY_API std::default_delete< MyClass >
{
public:
void operator()(MyClass *iToDelete);
};
Points of Interest
Having specialization of std
functions 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.
Following a comment below, I have added MyClassPluginBuilder
in the library, so that it is possible to instantiate MyClass
from an executable that would have opened the library at runtime like a plugin.
History
- v1.0 - 20th May, 2013: Initial release
- v1.1 - 26th August, 2014: Added
MyClassPluginBuilder
and ExecutableConsumerPlugin