Click here to Skip to main content
Click here to Skip to main content

How to ensure proper dynamic library boundary crossing for objects using smart pointers' custom deleters

, , 6 Dec 2013
Rate this:
Please Sign up or sign in to vote.
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 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.

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 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.

#include <ProblematicFactory.h>
#include <SafeFactory.h>

// change this undef to a define to see the heap corruption assert in debug
#undef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION
 
int main()
{
#ifdef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION
  {
    // this allocation is done the the DLL
    auto wMyObject = ProblematicFactory::getInstance().create();
    // this deallocation is done in the current assembly
    delete wMyObject;
    // when the DLL and this assembly are linked with the exact same CRT DLL,
    // the delete will work properly, otherwise, it will cause heap corruption.
  }
#endif
 
  {
    // always use auto when possible!
    auto wMyObject = SafeFactory::getInstance().create();
    // When the program will hit the following curly brace,
    // wMyObect will be automatically deleted
    // using the custom deleter provided in MyClass.h.
    // No need to send it back to a de-allocation
    // function of the library!
  } 
 
  {
    // std::unique_ptr can be automatically promoted to a std::shared_ptr
    // and the custom deleter follows, feels like magic!
    std::shared_ptr< MyClass > wMyObject = SafeFactory::getInstance().create();
    // Same behavior as the example above,
    // since the shared count will reach zero on the following
    // curly brace.
  }
  
  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 LIBRARYFACTORY_API ProblematicFactory
{
public:
  static ProblematicFactory & getInstance();
  
  MyClass * create() const;
 
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 encapsulate the raw pointer in a std::unique_ptr.

#pragma once
 
#include "DllSwitch.h"
#include "MyClass.h"

#include <memory>

class LIBRARYFACTORY_API SafeFactory
{
public:
  static SafeFactory & getInstance();
  
  // Note that this function will not be not part of the DLL, so no std::unique_ptr
  // crosses a library boundary! It is going to be built in the client's translation
  // units, therefore it uses the std::unique_ptr of the client. Also note that there
  // is no need to explicitely provide a custom deleter, since a specialization of
  // std::default_delete exists for the class MyClass (see MyClass.h).
  inline std::unique_ptr< MyClass > create() const
  {
    return std::unique_ptr< MyClass >(doCreate());
  }
 
private:
  SafeFactory();
 
  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.

#pragma once
 
#include "DllSwitch.h"

#include <memory>

class LIBRARYFACTORY_API MyClass
{
};
 
namespace std
{
 
// The following is a specialization of default_delete used by unique_ptr
// for the class MyClass. You need this for all types that the factory can create.
template<>
class LIBRARYFACTORY_API default_delete< MyClass >
{
public:
  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. 

History  

  • v1.0 - 2013-05-20: Initial release.

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Authors

Philippe Cayouette
Software Developer (Senior) CAE Inc.
Canada Canada
No Biography provided

Sebastien Gibeau
Software Developer (Senior)
Canada Canada
No Biography provided

Comments and Discussions

 
QuestionWill this work with plugins? PinmemberMember 1093767119hrs 6mins ago 
QuestionWorth price of admission... PinmemberAescleal5-Dec-13 23:42 
AnswerRe: Worth price of admission... PinmemberPhilippe Cayouette6-Dec-13 14:15 
GeneralRe: Worth price of admission... PinmemberAescleal8-Dec-13 22:31 
QuestionSource code not available PinmemberStone Free24-May-13 4:15 
AnswerRe: Source code not available PinmemberPhilippe Cayouette24-May-13 6:27 
GeneralRe: Source code not available Pinmemberpeterchen29-May-13 5:42 
GeneralRe: Source code not available PinmemberPhilippe Cayouette29-May-13 7:03 
GeneralRe: Source code not available Pinmemberpeterchen29-May-13 22:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140709.1 | Last Updated 6 Dec 2013
Article Copyright 2013 by Philippe Cayouette, Sebastien Gibeau
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid