Search code examples
c++boostshared-ptr

allocate_shared with additional byte buffer


Task: shared_ptr with T=buffer, where buffer has dynamic amount of bytes (uint8_t[] at the end);

Does allocate_shared guarantees that this order will be preserved: [shared_ptr data][object][additional butebuffer]

Because other way it won't work, for example: [object][shared_ptr data][additional butebuffer]

Other implementation ideas?

template <class T>
class addon_allocator : public boost::noncopyable
{
public : 
    typedef T value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;

public : 
    template<typename U> struct rebind
    {
        typedef addon_allocator<U> other;
    };

public : 
    explicit addon_allocator( std::size_t size )
        :m_size( size )
    {
    }

    ~addon_allocator()
    {
    }

    addon_allocator(const addon_allocator<T>& other)
        :m_size(other.m_size)
    {
    }

    template <class U>
    explicit addon_allocator(const addon_allocator<U>& other)
        :m_size(other.get_addon_size())
    {
    }

    pointer address(reference r)
    {
        return &r;
    }
    const_pointer address(const_reference r)
    {
        return &r;
    }

    pointer allocate(size_type cnt, typename std::allocator<void>::const_pointer = 0)
    {
        assert( cnt == 1 );
        return reinterpret_cast<pointer>(
            ::operator new(sizeof(T) + m_size)
            );
    }
    void deallocate(pointer p, size_type)
    {
        ::operator delete(p);
    }

    std::size_t get_addon_size() const
    {
        return m_size;
    }
private:
    const std::size_t m_size;
};

class buffer : public boost::noncopyable
{
public:
    buffer( std::size_t size )
        :m_size(size)
    {
    }
    const std::size_t m_size;
    uint8_t m_data[];
    static boost::shared_ptr<buffer> create(std::size_t size)
    {
        boost::allocate_shared<buffer>(
            addon_allocator<buffer>(size),
            size
            );
    }
};

Solution

  • Your code will also work with the implementation of std::allocate_shared in libstdc++ but I don't think there is any guarantee that the memory layout will be that way.

    Since you already have a specific function used to create the shared_ptr it would be more portable to do the memory allocation manually instead of using allocate_shared with your custom allocator:

    class buffer : public boost::noncopyable
    {
    public:
        buffer( std::size_t size ) throw()
            :m_size(size)
        {
        }
        const std::size_t m_size;
        uint8_t m_data[];
        static boost::shared_ptr<buffer> create(std::size_t size)
        {
            void* addr = ::operator new(sizeof(buffer) + size);
            return boost::shared_ptr<buffer>(::new(addr) buffer(size));
        }
    };
    

    This way there is no need for the allocator type, there's less code to maintain, and the code is all in one place. This does lose the advantage of allocate_shared only making a single allocation (and keeping the shared_ptr ref counting meta data adjacent to the object) but it is guaranteed to work on any implementation ... it's up to you whether that trade off is worth it.

    This code relies on the buffer constructor being no-throw, if it could throw the create function would need to catch the exception, deallocate the memory and rethrow.

    I would also consider making the constructor private so buffer objects can only be created by the create function.