2017-04-24 1 views
1

Ich speichere "Instanzen verschiedener Typen" mit "gemeinsamen Besitz". Das ist, was ich im Moment tun:Nicht-kopieren 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_; 
    ... 
} 

Ich würde gerne boost::any wechseln, all diese Abweichungen zu entfernen und gewann die Fähigkeit Instanzen zu speichern, wirklich jede Art. Auch ich mag boost::any Schnittstelle und boost::any_cast.

Aber meine Typen erfüllen nicht ValueType Anforderungen, sie sind nicht kopierbar. Was ist die beste (vorzugsweise vorhandene) Lösung für dieses Problem? Etwas wie shared_any_ptr, das Destruktor bei der Erstellung erfasst, hat Typ löschen, Referenzzähler und kann any_cast tun.

Edit: boost::any ermöglicht die Erstellung mit Umzug, aber ich würde lieber nicht einmal bewegen und Zeiger verwenden. Edit2: Ich benutze auch make_shared ausgiebig, so etwas make_shared_any_ptr würde sich als nützlich erweisen.

+1

Schreiben Sie Ihre eigenen 'any'? Es ist die einfachste aller Arten von Radierungen, also ist es eine gute Übung. – Barry

+0

@Barry Ich frage mich nur, ob es in Boost oder irgendwo eine existierende Lösung für das Problem gibt. – Anton3

+0

Sie könnten 'boost :: variant' verwenden, es ist auch viel typsicherer. –

Antwort

3

Dies ist nicht schwierig mit geteilten Zeigern. Wir können sogar Mehrfachzuteilungen vermeiden.

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; 
} 

man müßte any_cast implementieren, aber mit try_get<T> sollte es einfach sein.

Es kann einige Ecken Fälle wie const T geben, die das oben genannte nicht handhabt.

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 
} 

Auf diese Weise können Sie einen any_ptr nehmen und es in eine shared_ptr<T> wenn die Typen ohne das Kopieren nichts gefunden.

live example.

Sie werden feststellen, wie ähnlich any_block_made und any_block_dtor ist. Ich glaube, das ist der Grund, warum mindestens ein Major shared_ptr in einer std-Bibliothek den Ort wieder verwendet, an dem der Deleter für make_shared selbst lebt.

Ich könnte wahrscheinlich ähnliches tun, und Binärgröße hier reduzieren. Darüber hinaus ist die T/ Parameter von any_block_made und any_block_dtor wirklich nur darüber, wie groß und ausgerichtet der Speicherblock, mit dem wir spielen, ist, und was genau Typ gelöscht Helfer ich im dtor Zeiger im übergeordneten speichern. Ein Compiler/Linker mit COMDAT-Faltung (MSVC oder GOLD) kann hier den binären Bloat eliminieren, aber mit ein wenig Vorsicht könnte ich das selbst machen.

+0

Schön, ich war überrascht, dass ein solcher Zeiger in Boost immer noch nicht existiert.Es wäre noch besser, wenn es auch einen rohen Zeiger übernehmen könnte, aber ich bin trotzdem ziemlich glücklich damit. – Anton3

+0

@ Anton3 Das würde bedeuten, einen Zerstörer-Invoker und ein 'void *' zu 'any_block_base' hinzuzufügen, wobei' get_unsafe' in 'any_block_base' verschoben wird, mit einem vom Zerstörer abgeleiteten Typ von' any_block_base'. Dann füllen Sie den Basiszerstörer und void ptr in den Implementierungsfällen. Etwas mehr Overhead. – Yakk

+0

@ Anton3 Augmented & Live-Beispiel geschrieben. – Yakk

Verwandte Themen