Подтвердить что ты не робот

Почему бы не наследовать от std:: allocator

Я создал свой собственный распределитель следующим образом:

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::string и std::vector, но что не так с наследованием std::allocator?

Можно ли наследовать мой класс выше? Или мне нужен виртуальный деструктор для этого?

Почему?

4b9b3361

Ответ 1

Многие люди собираются публиковать в этом потоке, что вы не должны наследовать от std::allocator, потому что у него нет виртуального деструктора. Они будут говорить о полиморфизме и разрезании и удалении с помощью класса указатель-базовый, ни один из которых не разрешен требованиями распределителя, как подробно описано в разделе 17.6.3.5 [allocator.requirements] стандарта. Пока кто-то не продемонстрирует, что класс, полученный из std::allocator, не отвечает одному из этих требований, это простой менталитет культа.

Тем не менее, нет оснований извлекать из std::allocator в С++ 11. С++ 11 капитальный ремонт распределителей представил шаблон признаков std::allocator_traits, чтобы сидеть между распределителем и его пользователями и предоставлять разумные значения по умолчанию для многих необходимых функций с помощью метапрограммирования шаблонов. Минимальный распределитель в С++ 11 может быть таким же простым, как:

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);
}

EDIT: правильное использование std::allocator_traits пока не полностью присутствует во всех стандартных библиотеках. Например, вышеописанный распределитель выборки не работает с std::list при компиляции с GCC 4.8.1 - код std::list жалуется на отсутствие элементов, поскольку он еще не обновлен.

Ответ 2

Шаблон класса std::allocator<...> не имеет виртуальных функций. Таким образом, это явно плохой кандидат для предоставления производных функциональных возможностей. Хотя некоторые классы или шаблоны классов по-прежнему являются разумными базовыми классами, даже без виртуального деструктора и любой другой виртуальной функции, они имеют тенденцию быть либо просто типами тегов, либо использовать Любопытно повторяющийся шаблон шаблона.

Выделители не предназначены для такой настройки, т.е. 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>());
}

Ответ 3

Ну, деструктор не virtual. Это не является прямой проблемой, если вы не используете распределитель полиморфно. Но рассмотрим этот случай, где BasicAllocator наследует от std::allocator:

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

Деструктор BasicAllocator никогда не вызывается, что приводит к утечке памяти.

Ответ 4

Я просто попал в проблему в VS2013 (но он не отображается в VS2015) об этом. Наверное, не ответ на этот вопрос, но я собираюсь поделиться этим в любом случае:

В boost есть функция call_select_on_container_copy_construction(), проверяющая, имеет ли распределитель член select_on_container_copy_construction() и вызывает эту функцию, чтобы получить копию распределителя. В то время как std::allocator возвращает свою копию, производный myallocator должен переопределить этот метод, чтобы сделать то же самое и возвратить тип myallocator, вместо того чтобы оставить его наследованием с типом возврата std::allocator. Этот результат приводит к ошибке компиляции с непревзойденными типами.

Если myallocator наследует std::allocator, он должен переопределить любой родительский метод, у которого не может быть такого же типа возврата с типом, когда он переопределяется.

Примечание. Это видно только в VS2013, насколько я вижу, поэтому вы можете утверждать, что проблема с компилятором не является кодом.

myallocator Я использовал aligned_allocator в Eigen с версии 3.3.0.