为什么不从std :: allocator继承

时间:2014-01-12 23:44:55

标签: c++ inheritance c++11

我创建了自己的分配器,如下所示:

template<typename T>
class BasicAllocator
{
    public:
        typedef size_t size_type;
        typedef ptrdiff_t difference_type;
        typedef T* pointer;
        typedef const T* const_pointer;
        typedef T& reference;
        typedef const T& const_reference;
        typedef T value_type;


        BasicAllocator() throw() {};
        BasicAllocator (const BasicAllocator& other) throw() {};

        template<typename U>
        BasicAllocator (const BasicAllocator<U>& other) throw() {};

        template<typename U>
        BasicAllocator& operator = (const BasicAllocator<U>& other) {return *this;}
        BasicAllocator<T>& operator = (const BasicAllocator& other) {return *this;}
        ~BasicAllocator() {}

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

        pointer allocate (size_type n, const void* hint = 0) {return static_cast<pointer> (::operator new (n * sizeof (value_type) ) );}
        void deallocate (void* ptr, size_type n) {::operator delete (static_cast<T*> (ptr) );}

        template<typename U, typename... Args>
        void construct (U* ptr, Args&&  ... args) {::new (static_cast<void*> (ptr) ) U (std::forward<Args> (args)...);}
        void construct (pointer ptr, const T& val) {new (static_cast<T*> (ptr) ) T (val);}

        template<typename U>
        void destroy (U* ptr) {ptr->~U();}
        void destroy (pointer ptr) {ptr->~T();}

        size_type max_size() const {return std::numeric_limits<std::size_t>::max() / sizeof (T);} /**return std::size_t(-1);**/

        template<typename U>
        struct rebind
        {
            typedef BasicAllocator<U> other;
        };
};

但我想知道为什么我不应该继承std::allocator。是因为它没有虚拟析构函数吗?我看过很多帖子说应该创建自己的而不是继承。我理解为什么我们不应该继承std::stringstd::vector,但继承std::allocator有什么问题?

我可以继承上面的课吗?或者我需要一个虚拟析构函数来做到这一点?

为什么?

4 个答案:

答案 0 :(得分:28)

很多人都会在这个帖子中发帖,你不应该继承std::allocator,因为它没有虚拟的析构函数。他们将讨论多态性以及通过指针到基类的切片和删除,这些都不是分配器要求所允许的,如标准的第17.6.3.5节[allocator.requirements]中所述。直到有人证明来自std::allocator的类不能满足其中一个要求,这是简单的货物崇拜心态。

尽管如此,没有理由从C ++ 11中的std::allocator派生。 C ++ 11对分配器的大修引入了特征模板std::allocator_traits,位于分配器及其用户之间,并通过模板元编程为许多所需特性提供合理的默认值。 A minimal allocator in C++11 can be as simple as:

template <typename T>
struct mallocator {
  using value_type = T;

  mallocator() = default;
  template <class U>
  mallocator(const mallocator<U>&) {}

  T* allocate(std::size_t n) {
    std::cout << "allocate(" << n << ") = ";
    if (n <= std::numeric_limits<std::size_t>::max() / sizeof(T)) {
      if (auto ptr = std::malloc(n * sizeof(T))) {
        return static_cast<T*>(ptr);
      }
    }
    throw std::bad_alloc();
  }
  void deallocate(T* ptr, std::size_t n) {
    std::free(ptr);
  }
};

template <typename T, typename U>
inline bool operator == (const mallocator<T>&, const mallocator<U>&) {
  return true;
}

template <typename T, typename U>
inline bool operator != (const mallocator<T>& a, const mallocator<U>& b) {
  return !(a == b);
}

编辑:尚未正确使用std::allocator_traits所有标准库。例如,使用GCC 4.8.1编译时,上面的示例分配器无法与std::list一起正常工作 - std::list代码因为尚未更新而抱怨缺少成员。

答案 1 :(得分:7)

类模板std::allocator<...>没有任何虚函数。因此,提供衍生功能显然是不好的候选者。虽然一些类或类模板仍然是合理的基类,即使没有虚拟析构函数和任何其他虚函数,这些也可能只是标记类型或使用Curiously recurring template pattern

分配器不是这样定制的,即std::allocator<T>不是基本类。如果您尝试使用它,您的逻辑可能很容易被切掉。用于轻松自定义分配器的方法是依靠std::allocator_traits<A>来提供分配器选择不使用基于相对较少操作的默认实现明确提供的各种操作。

std::allocator<T>派生的主要问题是它可能隐藏rebind成员的问题,例如,成员被省略或拼写错误。下面是一个应该打印my_allocator::allocate()两次但不是由于拼写错误的示例。我认为my_allocator<T>除了错字之外是一个完整的分配器,即使没有来自std::allocator<T>的继承,也就是说,不必要的继承只会导致隐藏错误的可能性。您也可能会收到错误,例如,导致allocate()deallocate()功能错误。

#include <memory>
#include <iostream>

template <typename T>
struct my_allocator
    : std::allocator<T>
{
    my_allocator() {}
    template <typename U> my_allocator(my_allocator<U> const&) {}

    typedef T value_type;
    template <typename U> struct rebimd { typedef my_allocator<U> other; };
    T* allocate(size_t n) {
        std::cout << "my_allocator::allocate()\n";
        return static_cast<T*>(operator new(n*sizeof(T)));
    }
    void deallocate(T* p, size_t) { operator delete(p); }
};

template <typename A>
void f(A a)
{
    typedef std::allocator_traits<A>    traits;
    typedef typename traits::value_type value_type;
    typedef typename traits::pointer    pointer;
    pointer p = traits::allocate(a, sizeof(value_type));
    traits::deallocate(a, p, sizeof(value_type));

    typedef typename traits::template rebind_alloc<int> other;
    typedef std::allocator_traits<other> otraits;
    typedef typename otraits::value_type ovalue_type;
    typedef typename otraits::pointer    opointer;
    other o(a);
    opointer op = otraits::allocate(o, sizeof(ovalue_type));
    otraits::deallocate(o, op, sizeof(ovalue_type));
}

int main()
{
    f(my_allocator<int>());
}

答案 2 :(得分:0)

我刚刚在VS2013中遇到了一个问题(但它并没有出现在VS2015中)。可能不是这个问题的答案,但我还是要分享这个:

在boost中,有一个函数call_select_on_container_copy_construction()测试分配器是否有成员select_on_container_copy_construction()并调用该函数来获取分配器的副本。虽然std::allocator返回自身的副本,但派生的myallocator应该覆盖此方法以执行相同操作并返回myallocator类型,而不是让它继承std::allocator的类型返回类型。这导致编译错误,类型不匹配。

如果myallocator继承std::allocator,则必须覆盖任何父方法,这些方法可能与覆盖时的类型不具有相同的返回类型。

请注意,就我所见,这仅出现在VS2013中,因此您可能认为编译器存在问题而不是代码。

我在版本3.3.0中使用的myallocator在Eigen中是aligned_allocator

答案 3 :(得分:-1)

嗯,析构函数is not virtual。如果不以多态方式使用分配器,这不是直接问题。但请考虑这种情况,其中BasicAllocator继承自std::allocator

std::allocator<int>* ptr = new BasicAllocator<int>();
// ...
delete ptr;

BasicAllocator的析构函数从未被调用,导致内存泄漏。