10
votes

Comment utiliser une union de deux types

J'essaie de créer un vecteur code> qui peut contenir string code> et int code>.

J'ai essayé le code ci-dessous, mais Je reçois l'erreur de compilation p>

Erreur: Utilisation de la fonction supprimée 'my_union :: ~ my_union ()' p> BlockQuote>

Qu'est-ce que je fais mal? P>

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


9 commentaires

Rouverts: C ++ 11 a grandement élargi ce qui est autorisé dans une union et std :: string , entre autres, est maintenant autorisé.


@Petebecker Je reçois cela mais n'est pas couvert dans Ceci Réponse de la Q?


Comment envisagez-vous de «savoir» quel membre de l'Union à lire du vecteur plus tard?


@ M c'est une bonne question. Il n'y a qu'un petit nombre de chaînes possibles, alors j'allais vérifier l'élément de cordes pour l'égalité avec l'une de celles-ci, et si aucune correspondance, elle doit être un int. Je pense que cela ressemble à une idée terrible et évacuée par erreur.


@Nathanoliver - Cette question et cette réponse sont à partir de 2010, avant le changement qui a permis de cela. Un compilateur C ++ 11 doit Compiler le code qui met un std :: string dans une union, la réponse correcte à cette question est que Le compilateur est faux. La réponse liée, qui parle de la raison pour laquelle cela n'a aucun sens, c'est également faux.


@cppbeginner qui ne peut pas fonctionner. Le int chevauche la partie initiale de l'objet STD :: String . Donc s'il y avait en fait un int là, la représentation std :: string serait très corrompu en interne et que vous y accédez serait probablement résultats très étranges ou un crash. Formellement, il est un comportement indéfini pour accéder à un membre inactif d'une union.


@Angew tu as raison. Je ne le pensais pas. J'ai besoin de changer complètement mon approche.


@cppbeginner Vous voudrez peut-être consulter Boost :: Variante


@Angew merci, c'est très utile. C'est ce que je vais faire.


3 Réponses :


14
votes

de ici

Si un syndicat contient un élément de données non statique avec une fonction de membre spécial non trivial (constructeur par défaut, copier / déplacement constructeur, affectation de copie / déplacement ou destructeur), cette fonction est supprimée par défaut dans l'Union et les besoins être défini explicitement par le programmeur.

Vous devez définir explicitement un destructeur de votre syndicat pour remplacer celui-ci automatiquement supprimé pour chaîne .

Notez également que cela n'est valable que dans C ++ 11. Dans les versions antérieures, vous ne pouvez pas avoir de type avec des fonctions non triviales spéciales à l'intérieur d'une union du tout.

D'un point de vue pratique, cela ne peut toujours pas être une bonne idée.


2 commentaires

"Peut ... ne pas être une bonne idée" - en effet. Lorsque vous avez des types avec des constructeurs ou destructeurs non triviaux, vous devez utiliser des appels de placement nouveaux et explicites pour stocker un objet différent de l'Union.


@CODESINCHAOS Un commentaire à cette question fait allusion à Boost :: Variante < / a>. Je ne l'ai pas utilisé moi-même mais de la description sur la page Web, il semble que vous sonnez, même en donnant exactement la situation dans la question à titre d'exemple.



1
votes

avant C ++ 11, il n'a pas été autorisé à utiliser std :: chaîne dans un syndicat comme cité ici :

Les syndicats ne peuvent pas contenir un élément de données non statique avec un non-trivial Fonction de membre spéciale (constructeur de copie, opérateur d'affectation de copie, ou destructeur).

Et depuis C ++ 11, vous pouvez utiliser std :: String dans un syndicat Comme déjà répondu par @ rotem , vous devez définir un destructeur explicitement pour une chaîne ou appeler le destructeur explicitement xxx


0 commentaires

2
votes

Lorsque vous créez un syndicat avec une classe qui ne constitue pas fondamentalement des données anciennes, en C ++ 11, il vous permet. Mais cela va et supprime implicitement la plupart des fonctions membres spéciales telles que le destructeur.

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


0 commentaires