Boost shared_ptr:如何使用自定义删除器和分配器

4

自由函数allocate_shared可与任何符合标准的分配器一起使用。但是 shared_ptr 的构造函数和重置方法呢?

template<class Y, class D, class A> shared_ptr(Y * p, D d, A a);
template<class Y, class D, class A> void reset(Y * p, D d, A a);

手册中提到,D 应该提供一个调用运算符,用于删除指针,并且 A 必须是符合标准的分配器。如果是这样,为什么还需要 D?难道 A 不能同时完成分配和释放吗?你不觉得要求为每个自定义分配器提供删除器使得上述方法几乎没用吗? 当我使用自定义分配器时,我会使用 allocate_shared。如何知道正确的方式来释放由自定义分配器分配的内存? 编辑:通过对文本分配器和删除器进行一些实验后,我发现传递给 shared_ptr 构造函数和工厂函数 allocate_shared 的分配器仅用于分配 shared_ptr 的内部结构。 allocate_shared 永远不会使用传递的分配器来分配共享对象。我认为 boost 手册可以更明确地解释分配器的使用方式。

这里的部分信息不正确。allocate_shared 不仅为 shared_ptr 控制块使用分配器,还为对象本身使用分配器!它利用 allocator::rebind 来获取一个“兄弟分配器”,该分配器将分配足够大以容纳对象及其控制块的内存块。 - Ferdinand Beyer
1个回答

6
分配器旨在用于分配和释放内部的shared_ptr细节,而不是对象本身。
也就是说,虽然删除器使我们完全控制共享对象(因为我们控制它如何获取和释放),但分配器参数使我们控制对象共享特性的内部细节。
如果您查看N2351,分配器提案的末尾他们指出Boost已经实现了该功能,并链接到一个示例来演示其使用。
以下是该示例,逐字翻译:
#include <boost/config.hpp>

//  shared_ptr_alloc2_test.cpp
//
//  Copyright (c) 2005 Peter Dimov
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)


#include <boost/detail/lightweight_test.hpp>
#include <boost/shared_ptr.hpp>
#include <memory>
#include <cstddef>

// test_allocator

struct test_allocator_base
{
    int id_;

    static int last_global_id_;
    static int count_;

    explicit test_allocator_base( int id ): id_( id )
    {
    }
};

int test_allocator_base::last_global_id_ = 0;
int test_allocator_base::count_ = 0;

template<class T> class test_allocator: public test_allocator_base
{
public:

    typedef T * pointer;
    typedef T const * const_pointer;
    typedef T & reference;
    typedef T const & const_reference;
    typedef T value_type;
    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;

private:

    static T * last_pointer_;
    static std::size_t last_n_;
    static int last_id_;

public:

    template<class U> struct rebind
    {
        typedef test_allocator<U> other;
    };

    pointer address( reference r ) const
    {
        return &r;
    }

    const_pointer address( const_reference s ) const
    {
        return &s;
    }

    explicit test_allocator( int id = 0 ): test_allocator_base( id )
    {
    }

    template<class U> test_allocator( test_allocator<U> const & r ): test_allocator_base( r )
    {
    }

    template<class U> test_allocator & operator=( test_allocator<U> const & r )
    {
        test_allocator_base::operator=( r );
        return *this;
    }

    void deallocate( pointer p, size_type n )
    {
        BOOST_TEST( p == last_pointer_ );
        BOOST_TEST( n == last_n_ );
        BOOST_TEST( id_ == last_id_ );

        --count_;

        ::operator delete( p );
    }

    pointer allocate( size_type n, void const * )
    {
        T * p = static_cast< T* >( ::operator new( n * sizeof( T ) ) );

        last_pointer_ = p;
        last_n_ = n;
        last_id_ = id_;

        last_global_id_ = id_;
        ++count_;

        return p;
    }

    void construct( pointer p, T const & t )
    {
        new( p ) T( t );
    }

    void destroy( pointer p )
    {
        p->~T();
    }

    size_type max_size() const
    {
        return size_type( -1 ) / sizeof( T );
    }
};

template<class T> T * test_allocator<T>::last_pointer_ = 0;
template<class T> std::size_t test_allocator<T>::last_n_ = 0;
template<class T> int test_allocator<T>::last_id_ = 0;

template<class T, class U> inline bool operator==( test_allocator<T> const & a1, test_allocator<U> const & a2 )
{
    return a1.id_ == a2.id_;
}

template<class T, class U> inline bool operator!=( test_allocator<T> const & a1, test_allocator<U> const & a2 )
{
    return a1.id_ != a2.id_;
}

template<> class test_allocator<void>: public test_allocator_base
{
public:

    typedef void * pointer;
    typedef void const * const_pointer;
    typedef void value_type;

    template<class U> struct rebind
    {
        typedef test_allocator<U> other;
    };

    explicit test_allocator( int id = 0 ): test_allocator_base( id )
    {
    }

    template<class U> test_allocator( test_allocator<U> const & r ): test_allocator_base( r )
    {
    }

    template<class U> test_allocator & operator=( test_allocator<U> const & r )
    {
        test_allocator_base::operator=( r );
        return *this;
    }
};

//

struct X
{
    static int instances;

    X()
    {
        ++instances;
    }

    ~X()
    {
        --instances;
    }

private:

    X( X const & );
    X & operator=( X const & );
};

int X::instances = 0;

int main()
{
    BOOST_TEST( X::instances == 0 );

    boost::shared_ptr<void> pv( new X, boost::checked_deleter<X>(), std::allocator<X>() );

    BOOST_TEST( X::instances == 1 );

    pv.reset( new X, boost::checked_deleter<X>(), test_allocator<float>( 42 ) );

    BOOST_TEST( X::instances == 1 );

    BOOST_TEST( test_allocator_base::last_global_id_ == 42 );
    BOOST_TEST( test_allocator_base::count_ > 0 );

    pv.reset();

    BOOST_TEST( X::instances == 0 );
    BOOST_TEST( test_allocator_base::count_ == 0 );

    pv.reset( new X, boost::checked_deleter<X>(), test_allocator<void>( 43 ) );

    BOOST_TEST( X::instances == 1 );
    BOOST_TEST( test_allocator_base::last_global_id_ == 43 );

    pv.reset( new X, boost::checked_deleter<X>(), std::allocator<void>() );

    BOOST_TEST( X::instances == 1 );

    pv.reset();

    BOOST_TEST( X::instances == 0 );

    return boost::report_errors();
}

1
allocate_shared 怎么样?它是否使用分配器来分配内部和指针? - user401947
@rn141:不,它只是给shared_ptr分配你指定的分配器。基本上,非分配器构造函数就像make_shared一样,而分配器构造函数就像allocate_shared一样。 - GManNickG
抱歉,我不明白。allocate_shared 如何使用传递的分配器?allocate_shared 是否使用它来分配指针?allocate_shared 是否使用它来分配引用计数?您说删除器使我们完全控制共享对象。但是手册说删除器仅用于删除,而不是分配。 - user401947
@rn141:你明白分配器是用于处理细节,而删除器则删除对象吗?allocate_shared只是使用一个分配器来处理细节,而不是默认的。而删除器给了我们完全的控制权,因为我们自己可以分配对象。 - GManNickG
请注意,对于allocate_shared而言,此答案是不正确的,因为它同时使用分配器来分配对象和控制块! - Ferdinand Beyer
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接