2017-04-24 73 views
1

我用「共享所有權」存儲「不同類型的實例」。這就是我現在做的事:非複製std :: shared_ptr <boost::any>?

class Destructible { 
public: 
    virtual ~Destructible() = default; 
}; 

// UGLY 
class MyType1 : public Destructible { ... }; 
class MyTypeN : public Destructible { ... }; 

class Storage { 
    std::vector<std::shared_ptr<Destructible>> objects_; 
    ... 
} 

我很想轉boost::any,刪除所有這些符合項,並獲得存儲真正的任何類型的實例的能力。我也喜歡boost::any界面和boost::any_cast

但我的類型不滿足ValueType要求,它們不可複製。這個問題最好的(最好是現有的)解決方案是什麼?類似於shared_any_ptr,它在創建時捕獲析構函數,具有類型擦除,引用計數器並可以執行any_cast

編輯:boost::any允許創建與移動,但我寧願不移動和使用指針。編輯2:我也廣泛使用make_shared,所以make_shared_any_ptr會派上用場。

+1

寫你自己的'任何'?這是所有類型擦除中最簡單的,所以這是一個很好的練習。 – Barry

+0

@Barry只是想知道在Boost或其他地方是否有任何現有的解決方案。 – Anton3

+0

你可以使用'boost :: variant',它也是更安全的類型。 –

回答

3

這對共享指針並不棘手。我們甚至可以避免多個分配。

struct any_block { 
    any_block(any_block const&)=delete; 
    template<class T> 
    T* try_get() { 
    if (!info || !ptr) return nullptr; 
    if (std::type_index(typeid(T)) != std::type_index(*info)) return nullptr; 
    return static_cast<T*>(ptr); 
    } 
    template<class T> 
    T const* try_get() const { 
    if (!info || !ptr) return nullptr; 
    if (std::type_index(typeid(T)) != std::type_index(*info)) return nullptr; 
    return static_cast<T const*>(ptr); 
    } 
    ~any_block() { 
    cleanup(); 
    } 
protected: 
    void cleanup(){ 
    if (dtor) dtor(this); 
    dtor=0; 
    } 
    any_block() {} 
    std::type_info const* info = nullptr; 
    void* ptr = nullptr; 
    void(*dtor)(any_block*) = nullptr; 
}; 
template<class T> 
struct any_block_made:any_block { 
    std::aligned_storage_t<sizeof(T), alignof(T)> data; 
    any_block_made() {} 
    ~any_block_made() {} 
    T* get_unsafe() { 
    return static_cast<T*>((void*)&data); 
    } 
    template<class...Args> 
    void emplace(Args&&...args) { 
    ptr = ::new((void*)get_unsafe()) T(std::forward<Args>(args)...); 
    info = &typeid(T); 
    dtor = [](any_block* self){ 
     static_cast<any_block_made<T>*>(self)->get_unsafe()->~T(); 
    }; 
    } 
}; 
template<class D> 
struct any_block_dtor:any_block { 
    std::aligned_storage_t<sizeof(D), alignof(D)> dtor_data; 
    any_block_dtor() {} 
    ~any_block_dtor() { 
    cleanup(); 
    if (info) dtor_unsafe()->~D(); 
    } 
    D* dtor_unsafe() { 
    return static_cast<D*>((void*)&dtor_data); 
    } 
    template<class T, class D0> 
    void init(T* t, D0&& d) { 
    ::new((void*)dtor_unsafe()) D(std::forward<D0>(d)); 
    info = &typeid(T); 
    ptr = t; 
    dtor = [](any_block* s) { 
     auto* self = static_cast<any_block_dtor<D>*>(s); 
     (*self->dtor_unsafe())(static_cast<T*>(self->ptr)); 
    }; 
    } 
}; 

using any_ptr = std::shared_ptr<any_block>; 
template<class T, class...Args> 
any_ptr 
make_any_ptr(Args&&...args) { 
    auto r = std::make_shared<any_block_made<T>>(); 
    if (!r) return nullptr; 
    r->emplace(std::forward<Args>(args)...); 
    return r; 
} 
template<class T, class D=std::default_delete<T>> 
any_ptr wrap_any_ptr(T* t, D&& d = {}) { 
    auto r = std::make_shared<any_block_dtor<std::decay_t<D>>>(); 
    if (!r) return nullptr; 
    r->init(t, std::forward<D>(d)); 
    return r; 
} 

你必須執行any_cast,但try_get<T>應該很容易。

可能有一些像上面沒有處理的const T這樣的角落案例。

template<class T> 
std::shared_ptr<T> 
crystalize_any_ptr(any_ptr ptr) { 
    if (!ptr) return nullptr; 
    T* pt = ptr->try_get<T>(); 
    if (!pt) return nullptr; 
    return {pt, ptr}; // aliasing constructor 
} 

這可以讓你把一個any_ptr並把它變成一個shared_ptr<T>如果類型匹配而不復制任何東西。

live example

您會注意到any_block_madeany_block_dtor有多相似。我相信這就是爲什麼std庫中至少有一個主要的shared_ptr重用了刪除者爲make_shared本身所處的位置。

我大概可以做類似的事情,並在這裏減少二進制大小。另外,any_block_madeany_block_dtorT/D參數實際上只是關於我們所玩的內存塊的大小和對齊方式,以及我在親本指針dtor中存儲的確切類型的擦除輔助程序。使用COMDAT摺疊(MSVC或GOLD)的編譯器/鏈接器可能會消除此處的二進制膨脹,但有一點小心,我可以自己做。

+0

不錯,我很驚訝這樣的指針在Boost中還是不存在。如果它也可以擁有一個原始指針的話,它會更好,但我仍然對它很滿意。 – Anton3

+0

@ Anton3這隻需要將一個驅逐者調用者和一個void *添加到any_block_base中,將get_unsafe移動到any_block_base中,並且該驅動程序只有派生類型的any_block_base。然後在實現案例中填充基礎驅逐者和void ptr。稍微多一點的開銷。 – Yakk

+0

@ Anton3增加了活動示例發佈。 – Yakk

相關問題