2014-01-12 55 views
13

我創造了我自己的分配器像這樣:爲什麼不從繼承的std ::分配器

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有什麼問題?

我可以繼承我的課程嗎?或者我需要一個虛擬析構函數來做到這一點?

爲什麼?

+0

'new(static_cast (ptr))'看起來像一個打字錯誤(該函數已經在使用'pointer == T *') – dyp

回答

25

很多人會在這個線程,你不應該從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代碼由於尚未更新而抱怨失蹤的成員。

+0

我喜歡這個!我不知道如果沒有明確地創建我的分配器中的每個成員,我都可以做你在那裏的事情。這次真是萬分感謝!我想我不得不實施allocator_traits一些如何。我不知道容器使用它。 =)這正是我想知道的。 – Brandon

+0

對於你的信息,嘗試std :: list >與libC++和clang自己的C++ abi進行鏗鏘聲,並且編譯正常。 – user534498

+0

@ user534498 libC++從未打算支持早於C++ 11的語言版本,所以我並不感到驚訝,它在allocator_traits支持方面比libstdC++更進一步。感謝您的驗證。 – Casey

0

那麼,破壞者is not virtual。如果不多次使用分配器,這不是一個直接的問題。但考慮這種情況下,來自std::allocatorBasicAllocator繼承:

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

BasicAllocator析構函數永遠不會被調用,從而導致內存泄漏。

+4

沒有在標準的分配器需求細節中它說分配器必須支持通過指向其基類之一的指針刪除。你提出了一個很好的理由,不要把一個類從另一個類派生出來,而不需要一個多態的虛析構函數,但是它沒有回答OP的問題,因爲它不適用於分配器的具體情況。 – Casey

8

班級模板std::allocator<...>沒有任何虛擬功能。因此,提供衍生功能顯然是一個糟糕的選擇。雖然某些類或類模板仍然是合理的基類,但即使沒有虛擬析構函數和任何其他虛函數,它們也可能只是標記類型或使用Curiously recurring template pattern

分配器不打算像那樣定製,即,std::allocator<T>不是作爲基類。如果你試圖像這樣使用它,你的邏輯可能很容易被切斷。用於輕鬆定製分配器的方法是依靠std::allocator_traits<A>來提供分配器選擇不基於相對少量的操作顯式使用默認實現提供的各種操作。

關於從std::allocator<T>派生出來的主要問題是它可能會隱藏成員的問題,例如成員被忽略或拼寫錯誤。下面是一個例子,它應該打印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:當然。明顯的罪魁禍首是省略或拼寫'rebind'。我已經更新了答案。 –

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我使用的是自版本3.3.0起的Eigen中的aligned_allocator