2013-04-12 50 views
4

從之後,我想用一個unitialised_allocator用,比如說,std::vector避免在建築元素(或std::vector(的resize()的默認初始化也看到here的使用情況)我現在的設計是這樣的:。如何使我的uninitialised_allocator安全?

// based on a design by Jared Hoberock 
template<typename T, typename base_allocator > 
struct uninitialised_allocator : base_allocator::template rebind<T>::other 
{ 
    // added by Walter Q: IS THIS THE CORRECT CONDITION? 
    static_assert(std::is_trivially_default_constructible<T>::value, 
       "value type must be default constructible"); 
    // added by Walter Q: IS THIS THE CORRECT CONDITION? 
    static_assert(std::is_trivially_destructible<T>::value, 
       "value type must be default destructible"); 
    using base_t = typename base_allocator::template rebind<T>::other; 
    template<typename U> 
    struct rebind 
    { 
    typedef uninitialised_allocator<U, base_allocator> other; 
    }; 
    typename base_t::pointer allocate(typename base_t::size_type n) 
    { 
    return base_t::allocate(n); 
    } 
    // catch default construction 
    void construct(T*) 
    { 
    // no-op 
    } 
    // forward everything else with at least one argument to the base 
    template<typename Arg1, typename... Args> 
    void construct(T* p, Arg1 &&arg1, Args&&... args)default_ 
    { 
    base_t::construct(p, std::forward<Arg1>(arg1), std::forward<Args>(args)...); 
    } 
}; 

然後一個unitialised_vector<>的模板,可以定義如下:

template<typename T, typename base_allocator = std::allocator<T>> 
using uninitialised_vector = 
    std::vector<T,uninitialised_allocator<T,base_allocator>>; 

但是,我的意見所指示的,我不是100%確定爲static_assert()中有什麼合適的條件?(順便說一句,可以考慮改爲SFINAE - 在這個任何有用的意見,歡迎)

顯然,人們必須避免將來自未初始化的對象的企圖不平凡的破壞隨之而來的災難。考慮

unitialised_vector< std::vector<int> > x(10); // dangerous. 

有人建議(由葉夫根Panasyuk評論),我斷言瑣碎的構造性,但是這似乎並沒有趕上以上的災難場景。我只是試圖檢查什麼鏗鏘說關於std::is_trivially_default_constructible<std::vector<int>>(或std::is_trivially_destructible<std::vector<int>>),但我得到的是崩潰的叮噹3.2 ...

另一個更先進的選項將設計一個分配器,它只能使用默認構造爲此可以安全的物體。

+0

你可能也想的Elid完全'任何電話destruct',只要類型是微不足道的。 – Xeo

+0

@Xeo好點!將以類似的方式添加。實際上,爲什麼標準不支持這種類型的微不足道的優化? – Walter

+0

@Xeo雖然我會想到,在大多數危急情況下,瑣碎類型的破壞會被優化掉。 – Walter

回答

4

FWIW,我認爲設計可被簡化,假設一個C++ 11符合容器:

template <class T> 
class no_init_allocator 
{ 
public: 
    typedef T value_type; 

    no_init_allocator() noexcept {} 
    template <class U> 
     no_init_allocator(const no_init_allocator<U>&) noexcept {} 
    T* allocate(std::size_t n) 
     {return static_cast<T*>(::operator new(n * sizeof(T)));} 
    void deallocate(T* p, std::size_t) noexcept 
     {::operator delete(static_cast<void*>(p));} 
    template <class U> 
     void construct(U*) noexcept 
     { 
      static_assert(std::is_trivially_default_constructible<U>::value, 
      "This allocator can only be used with trivally default constructible types"); 
     } 
    template <class U, class A0, class... Args> 
     void construct(U* up, A0&& a0, Args&&... args) noexcept 
     { 
      ::new(up) U(std::forward<A0>(a0), std::forward<Args>(args)...); 
     } 
}; 
  1. 我看到小優點從另一個分配器導出。

  2. 現在您可以讓allocator_traits處理rebind

  3. 模板U上的construct成員。如果您想將此分配器用於需要分配除T(例如std::list)以外的某個容器,則這會有所幫助。

  4. static_assert測試移入單個construct成員中,它是重要的。

仍然可以創建一個using

template <class T> 
using uninitialised_vector = std::vector<T, no_init_allocator<T>>; 

而這仍然無法編譯:

unitialised_vector< std::vector<int> > x(10); 


test.cpp:447:17: error: static_assert failed "This allocator can only be used with trivally default constructible types" 
       static_assert(std::is_trivially_default_constructible<U>::value, 
       ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

我覺得測試is_trivially_destructible是矯枉過正,除非您還優化destroy到沒做什麼。但我認爲沒有這樣做的動機,因爲我認爲無論如何它應該在適當的時候進行優化。沒有這樣的限制,你可以:

class A 
{ 
    int data_; 
public: 
    A() = default; 
    A(int d) : data_(d) {} 
}; 

int main() 
{ 
    uninitialised_vector<A> v(10); 
} 

它只是工作。但是,如果你~A()不平凡:

~A() {std::cout << "~A(" << data_ << ")\n";} 

然後,至少在我的系統,你在建設得到一個錯誤:

test.cpp:447:17: error: static_assert failed "This allocator can only be used with trivally default constructible types" 
       static_assert(std::is_trivially_default_constructible<U>::value, 
       ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

即如果它有一個非平凡的析構函數,那麼A就不再是可以構造的了。

然而,即使與不平凡的析構函數,你仍然可以:

uninitialised_vector<A> v; 
    v.push_back(A()); 

這工作,只有,因爲我沒有與需要瑣碎的析構函數過火。而執行這個時候,我得到~A()按預期運行:

~A(0) 
~A(0) 
+0

*我發現從另一個分配器派生出來的優勢很小*例如,爲了確保所需的對齊方式(通過使用保證的分配器),這非常重要。從另一個分配器中派生出來沒有什麼壞處,所以不要做那些看起來很愚蠢的事(如果它可能會有幫助的話)。 – Walter

+0

嗯。有沒有一個簡單的默認構造函數,但不平凡的析構函數的一個很好的例子?如果是這樣,在上面的設計中使用是否安全(沒有重寫'destroy()')?這對於真正回答我的問題至關重要。 – Walter

+0

要點2-4是好的,但我不認爲你需要聲明構造函數。畢竟,這是一個微不足道的類型。 – Walter