2016-05-04 16 views
10

Sto cercando di creare un vector che può contenere string e int.Come utilizzare un unione di due tipi

ho provato il codice qui sotto, ma ho l'errore di compilazione

error: use of deleted function 'my_union::~my_union()'

Che cosa sto facendo di sbagliato?

#include <iostream> 
#include <vector> 

using namespace std; 

union my_union 
{ 
    string str; 
    int a; 
}; 

int main() 
{ 
    vector<my_union> v; 
    my_union u;   // error: use of deleted function 'my_union::~my_union()' 
    u.str = "foo"; 
    v.push_back(u); 
    return 0; 
} 
+0

Riaperto: C++ 11 ha ampliato notevolmente ciò che è consentito in un sindacato e 'std :: string', tra gli altri, è ora consentito. –

+1

@PeteBecker Ricevo quello ma non è coperto da [questo] (http://stackoverflow.com/a/3521998/4342498) risposta dalla Q? – NathanOliver

+2

Come pensate di "sapere" quale membro dell'unione leggere successivamente dal vettore? –

risposta

14

Da here

If a union contains a non-static data member with a non-trivial special member function (default constructor, copy/move constructor, copy/move assignment, or destructor), that function is deleted by default in the union and needs to be defined explicitly by the programmer.

È necessario definire in modo esplicito un distruttore per la vostra unione per sostituire quello automaticamente cancellato per string.

Si noti inoltre che questo è valido solo in C++ 11. Nelle versioni precedenti non è possibile avere un tipo con funzioni speciali non banali all'interno di un sindacato.

Da un punto di vista pratico, questo potrebbe non essere ancora una grande idea.

+4

"potrebbe ... non essere una buona idea" - anzi. Quando si hanno tipi con costruttori o distruttori non banali, è necessario utilizzare le chiamate di distruttore nuove ed esplicite per archiviare un oggetto di un tipo diverso nell'unione. –

+1

@CodesInChaos Un commento a questa domanda allude a [boost :: variant] (http://www.boost.org/doc/libs/1_60_0/doc/html/variant.html). Non l'ho usato da solo, ma dalla descrizione sulla pagina web sembra azzeccato, anche dando esattamente la situazione nella domanda come un esempio. – Rotem

1

Prima di C++ 11, non è stato permesso di utilizzare std::string in un'unione come citato here:

Unions cannot contain a non-static data member with a non-trivial special member function (copy constructor, copy-assignment operator, or destructor).

E poiché C++ 11 è possibile utilizzare std::string in un'unione, come già risposto da @Rotem, è necessario definire un distruttore esplicitamente stringa o chiamare il distruttore explicitly

str.~basic_string<char>(); 
2

Quando si crea un'unione con una classe che non è fondamentalmente semplice vecchi dati, in C++ 11 ti lascia. Ma va e cancella implicitamente la maggior parte delle funzioni membro speciali come il distruttore.

union my_union 
{ 
    string str; 
    int a; 
}; 

il problema pratico è che al punto di distruzione di C++ non sa che dei componenti su riportati dell'unione sono validi.

È possibile aggirare il problema utilizzando un'unione con tag e mantenendo la traccia attiva e manualmente la distruzione in quel caso.

Così possiamo ottenere qualcosa di simile:

struct tagged_union { 
    enum active {nothing, string, integer} which_active; 
    template<active...As> 
    using actives = std::integral_sequence<active, As...> 
    using my_actives = actives<nothing, string, integer>; 

    struct nothingness {}; 

    union my_union 
    { 
    nothingness nothing; 
    std::string str; 
    int a; 
    ~my_union() {}; 
    } data; 
    using my_tuple = std::tuple<nothingness, std::string, int>; 

    template<active which> 
    using get_type = std::tuple_element_t<(std::size_t)which, my_tuple>; 

    template<class F> 
    void operate_on(F&& f) { 
    operate_on_internal(my_actives{}, std::forward<F>(f)); 
    } 
    template<class T, class F> 
    decltype(auto) operate_on_by_type(F&& f) { 
    return std::forward<F>(f)(reinterpret_cast<T*>(&data)); 
    } 
    // const versions go here 
private: 
    // a small magic switch: 
    template<active...As, class F>  
    void operate_on_internal(actives<As...>, F&& f) { 
    using ptr = void(*)(my_union*,std::decay_t<F>*); 
    const ptr table[]={ 
     [](my_union* self, std::decay_t<F>* pf){ 
     std::forward<F>(*pf)(*(get_type<As>*)self); 
     }..., 
     nullptr 
    }; 
    table[which](&data, std::address_of(f)); 
    } 
public: 
    template<class...Args> 
    tagged_union(Active w, Args&&...args) { 
    operate_on([&](auto& t){ 
     using T = std::decay_t<decltype(t)>(); 
     ::new((void*)std::addressof(t)) T(std::forward<Args>(args)...); 
     which = w; 
    }); 
    } 
    tagged_union():tagged_union(nothing){} 

    ~tagged_union() { 
    operate_on([](auto& t){ 
     using T = std::decay_t<decltype(t)>(); 
     t->~T(); 
     which=nothing; 
     ::new((void*)std::addressof(t)) nothingness{}; // "leaks" but we don't care 
    }); 
    } 
}; 

che è fondamentalmente uno schizzo primitivo di come qualcosa di simile boost::variant lavori se scritto in C++ 11.

Comporta qualche mojo pesante.

Quanto sopra non è stato compilato, ma il design è valido. Alcuni compilatori C++ 14 nominalmente non gradiscono che un pacco si espanda attorno a un lambda completo, il che richiederebbe un numero ancora maggiore di piatti.