2014-01-12 3 views
13

Я создал свой собственный аллокатора так:Почему бы не наследовать от станда :: Распределителя

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?

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

Почему?

+0

'new (static_cast (ptr))' выглядит как опечатка (функция уже принимает указатель == T * ') – dyp

ответ

25

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

Тем не менее, нет оснований извлекать из std::allocator в C++ 11. 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); 
} 

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

+0

Мне это нравится! Не знал, что я могу делать то, что у вас есть, без явного создания каждого члена, который у меня есть в моем распределителе. Спасибо тебе за это! Я думал, что мне придется реализовать allocator_traits каким-то образом. Я не знал, что контейнеры использовали его. =) Это именно то, что я хотел знать. – Brandon

+0

Для вашей информации, попробовал std :: list > на clang с libC++ и собственный C++ C++ abi, и скомпилирован отлично. – user534498

+0

@ user534498 libC++ никогда не предназначался для поддержки языковых версий раньше, чем C++ 11, поэтому я не удивлен, что он поддерживается поддержкой allocator_traits, чем libstdC++. Спасибо за проверку. – Casey

0

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

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

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

+4

Нет, где в стандартном описании требований распределителя говорится, что распределитель должен поддерживать удаление через указатель на один из его базовых классов. Вы представляете серьезную причину не относиться к классу, полученному из другого класса, без виртуального деструктора полиморфно вообще, но он не отвечает на вопрос ОП, поскольку он не применяется к конкретному случаю распределителей. – Casey

8

Класс шаблона 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>()); 
} 
+0

Я не могу найти примеры использования 'std :: allocator_traits' или того, для чего он предназначен. Я также заметил, что библиотеки используют распределитель, подобный моему и наследующий от него без виртуального деструктора. Я где-то читал, что распределитель не должен иметь виртуальных функций. http://gcc.gnu.org/ml/libstdc++/2011-05/msg00120.html – Brandon

+1

Можете ли вы продемонстрировать, какая из операций, описанных в требованиях распределителя (C++ 11 17.6.3.5), имеет потенциал для отсечения логики ? – Casey

+0

@Casey: конечно. Очевидным преступником является пропущение или нарушение орфографии. Я обновил ответ. –

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, насколько я вижу, поэтому вы можете утверждать, что это проблема с компилятором, а не с кодом.

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

Смежные вопросы