Boost logo

Boost :

Subject: [boost] [shared_ptr] A smarter smart pointer proposal for dynamic libraries
From: J.D. Herron (jotadehace_at_[hidden])
Date: 2008-12-23 17:45:00


I have a suggestion for the shared pointer implementation to overcome a
severe limitation it has currently regarding dynamically unloaded
libraries. I posted a little while back regarding this problem which is
illustrated with the following example from a Microsoft Windows
environment. I have a simple factory interface like so:

class Factory
{
public:
    virtual shared_ptr<int> GetIntSharedPtr() = 0;
    virtual int * GetIntRawPtr() = 0;
};

I implement a factory DLL to provide one of these factories that has an
exported function GetFactory() like so:

/// implementation of the Factory interface
class TestFactory : public Factory
{
public:
    virtual shared_ptr<int> GetIntSharedPtr()
    {
        return shared_ptr<int>( new int(5) );
    }

    virtual int * GetIntRawPtr()
    {
        return new int(0);
    }
};

/// Library instance of the factory implementation
TestFactory g_Factory;

/// Exported function to return the factory for this library
_declspec(dllexport) extern "C" Factory & GetFactory()
{
    return g_Factory;
}

This is compiled into a library named "TestFactory.dll". Now I create a
simple command line application that does the following:

/// Typedefinition for for the GetFactory exported function
typedef Factory & (_cdecl * GetFactory)();

int main()
{
    // dynamically load the TestFactory library
    HMODULE hFactoryDll = ::LoadLibrary( "TestFactory.dll" );

    // get the GetFactory() interface from the loaded library
    GetFactory pfnGetFactory = ::GetProcAddress( hFactoryDll, "GetFactory"
);

    // Acquire the factory object
    Factory & factory = pfnGetFactory();

    // Call the factory interfaces to get dynamically allocated integers
    int * pRawInt = factory.GetIntRawPtr();

    shared_ptr<int> spInt = factory.GetIntSharedPtr();

    // everything is fine so far, now unload the factory library
    ::FreeLibrary( hFactoryDll );

    // deallocating the raw pointer recieved from the factory works just
fine
    delete pRawInt; // THIS WORKS!

    // However due to the reasons outlined below, releasing the shared_ptr
causes
    // an access violation
    spInt.reset() // THIS CRASHES!
}

The basic problem is that the shared_ptr implementation uses virtual
functions in the sp_counted_base class that gets generated in the factory
dll since it is a template instantiation and once that factory dll is
unloaded the virtual function tables for all shared_ptrs it generated are
now garbage. In my opinion the virtual function implementation in the
template is an unnecessary design flaw that creates this limitation. I also
think the reference counter just simply does too much. I would propose an
alternative design that eliminated the use of virtual functions in template
generated base classes and let the outer smart pointer class handle the
deallocation chores and make the reference counter just a simple reference
counter. This could eliminate the limitation illustrated in this simple and
very common use case. Ignoring a bunch of stuff right now like custom
deallocators, weak references, some basic members like assignment, etc.,
here is a simple reference counted smart pointer that is able to handle this
library unloading example without a problem just as you could do with a raw
pointer:

/// reference counter THAT ONLY REFERENCE COUNTS AND HAS NO VTABLE
class SimpleRefCounter
{
public:

  /// Constructor initializes reference count to 0
  SimpleRefCounter( long count = 0 ) : m_Count(count) {}

  /// Increments the reference count and returns it
  long Increment()
  {
      return ::InterlockedIncrement( &m_Count );
  }

  /// Decrements the reference count and returns it
  long Decrement()
  {
      return ::InterlockedDecrement( &m_Count );
  }

  /// Returns the current reference count
  long Count() const { return m_Count; }

private:

  long m_Count;
};

/// Simple smart pointer template that handles the deallocation chores and
therefor
/// can be used safely across dynamic dll boundaries
template < typename _type >
class SmarterPtr
{
public:

    /// Default constructor creates a null pointer
    SmarterPtr() : m_pValue( NULL ), m_pRefCounter( NULL )
    {}

    /// Constructor taking an allocated value to manage
    SmarterPtr( _type * p_pValue ) : m_pValue( p_pValue )
    {
        m_pRefCounter = new SimpleRefCounter( 1 );
    }

    /// Copy constructor
    SmarterPtr( const SmarterPtr & p_Original )
        : m_pValue( p_Original.m_pValue ),
          m_pRefCounter( p_Original.m_pRefCounter )
    {
        m_pRefCounter->Increment();
    }

    /// Destructor releases
    ~SmarterPtr()
    {
        reset();
    }

    /// Resets the managed pointer to the passed in one releasing the
    /// currently managed pointer and deallocating if necessary
    void reset( _type * pNewValue = NULL )
    {
        if ( pNewValue != m_pValue )
        {
            if ( m_pValue && 0 == m_pRefCounter->Decrement() )
            {
                // need to deallocate current value
                delete m_pValue;
                delete m_pRefCounter; // ignore weak ref for now
                m_pValue = NULL;
                m_pRefCounter = NULL;
            }

            if ( pNewValue )
            {
                m_pRefCounter = new SimpleRefCounter( 1 );
                m_pValue = pNewValue;
            }
        }
    }

private:

    _type * m_pValue;
    SimpleRefCounter * m_pRefCounter;
};

Again the key is no VTABLE and deallocation handled by the exposed
templates. Why shouldn't shared_ptr move to an equivalent model so it can
be used across dynamic dll boundaries safely?

J.D.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk