2017-04-24 15 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は、移動で作成することができますが、移動してポインタを使用したくない場合もあります。 Edit2:make_sharedも広く使用していますので、何かmake_shared_any_ptrが便利です。

+1

あなた自身で 'any'と書いてください。それはすべてのタイプの消去の中で最もシンプルなので、良い演習です。 – 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の類似度に気づくでしょう。私は、これが、標準ライブラリの少なくとも1つの主要なshared_ptrが、make_shared自身のためにDeleterが住んでいる場所を再利用する理由だと考えています。

私はおそらく同様のことができ、ここでバイナリサイズを減らすことができます。さらに、T/Dのパラメータがany_block_madeany_block_dtorの場合、私たちが再生するメモリのブロックはどれくらい大きく、整列しているのですか?また、正確にタイプ消去されたヘルパーは、親のポインタdtorに格納されています。 COMDATフォールディング(MSVCまたはGOLD)を使用したコンパイラ/リンカーは、ここではバイナリの肥大化を解消するかもしれませんが、ちょっとした注意を払って自分でやることができます。

+0

ニース、私はそのようなポインタがBoostにまだ存在しないことに驚いた。それは、それが生のポインタを介して所有権を取ることができればさらに良いだろうが、とにかくそれにはかなり満足している。 – Anton3

+0

@ Anton3これは、destroyer-invokerと 'void *'を 'any_block_base'に追加し、' get_unsafe'を 'any_block_base'に移動し、destroyer-only派生型' any_block_base'を持っています。次に、実装のケースでは、ベース・デストラクタとvoid ptrを設定します。少しオーバーヘッド。 – Yakk

+0

@ Anton3拡張されたライブの例が掲載されました。 – Yakk

関連する問題