4
votes

Le constructeur par défaut ne peut pas être référencé lors de l'utilisation de std :: string dans l'union membre d'une structure

J'ai une structure très basique qui a une énumération et une union.

typedef struct
{
    enum v{a,b,c}v;
    union w{
        int a;
        bool b;
        std::string c;
    }; // changed here.

}Data_Set2;

En utilisant une telle structure, j'obtiens le code d'erreur C2280 indiquant que le constructeur par défaut ne peut pas être référencé. Quand je déclare la structure d'une manière légèrement différente comme suit

typedef struct
{
    enum v{a,b,c}v;
    union w{
        int a;
        bool b;
        std::string c;
    }w;

}Data_Set2;

int main()
{
Data_Set2 val; // Shows errror that the default constructor cannot be referenced
return 0;
}

L'erreur n'existe plus. Je ne comprends pas la raison derrière cela. Quelqu'un pourrait-il expliquer pourquoi cela se produit


1 commentaires

Remarque: jetez un œil à std :: any et std :: variant . Ce dernier fournit des unions de type sûr et serait probablement le meilleur choix dans votre cas. Notez que votre compilateur (apparemment MSVC) doit prendre en charge C ++ 17.


3 Réponses :


2
votes

Dans le premier exemple, vous définissez une variable membre, qui doit être constructible par défaut. Dans le deuxième exemple, vous définissez un membre type , qui donnera la même erreur si vous l'utilisez pour définir une variable de ce type.

Quant à l'erreur, vous devez créer un constructeur par défaut dans l'union pour pouvoir l'initialiser correctement:

union w{
    int a;
    bool b;
    std::string c;

    // Default constructor initialize the string member
    w() : c()
    {}
}w;


2 commentaires

@Quelque programmeur mec. Même après avoir utilisé le code que vous avez fourni, j'obtiens toujours la même erreur.


@eerorika L'initialiseur de membre par défaut ne résout pas non plus le problème.



4
votes

Le problème est que l'union w n'est ni constructible par défaut, ni destructible. Le constructeur par défaut et le destructeur ne sont pas générés implicitement, car le membre c n'est pas trivialement constructible, ni trivialement destructible. En tant que tel, avoir un membre de type w n'est tout simplement pas possible. Dans votre deuxième exemple, vous supprimez le membre, donc il n'y a pas de problème.

Afin de rendre w constructible par défaut, vous pouvez définir un constructeur par défaut:

    ~w(){
        //TODO destruct the active member
    }
} w;
  • Il n'est pas possible de savoir quel membre est actif.
  • L'accès à un membre inactif a un comportement non défini
  • Si c est actif, ne pas le détruire a un comportement indéfini

En conclusion: vous devez vous assurer que w n'est jamais détruit pendant que le membre c est actif. Un tel invariant pourrait être implémenté dans le destructeur de Data_Set2 , en supposant que v indique quel membre est actif (qui est un autre invariant qui devrait être maintenu; ces membres ne devraient probablement pas être publics) .


0 commentaires

4
votes

Depuis https://en.cppreference.com/w/cpp/language/ union (ou consultez la norme ):

Si une union contient un membre de données non statique avec une fonction membre spéciale non triviale (constructeur de copie / déplacement, affectation de copie / déplacement ou destructeur), cette fonction est supprimée par défaut dans l'union et doit être définie explicitement par le programmeur.

Si une union contient un membre de données non statique avec un constructeur par défaut non trivial, le constructeur par défaut de l'union est supprimé par défaut à moins qu'un membre variant de l'union n'ait un initialiseur de membre par défaut.

Au plus un membre variant peut avoir un initialiseur de membre par défaut.

Dans votre cas, cela signifie que vous devez déclarer explicitement un constructeur et un destructeur. Changez votre code en:

typedef struct
{
    enum v{a,b,c} v;
    union w{
        int a;
        bool b;
        std::string c;
        w() {}       // Explicit constructor definition
        ~w() { };    // Explicit destructor definition
} w;

} Data_Set2;

Cela devrait fonctionner.

Comme déjà indiqué dans mon commentaire, vous devriez cependant jeter un œil à std: : tout et std :: variant . Ce dernier fournit des unions de type sécurisé et serait probablement le meilleur choix dans votre cas. Notez que votre compilateur (apparemment MSVC) doit prendre en charge C ++ 17.

EDIT: Comme l'a commenté eerorika, vous devrez vous assurer que vous ne l'appelez que sur le membre actuellement actif. La référence liée au début montre un exemple d'union chaîne / vecteur et comment elle introduit de nombreux pièges pour un comportement indéfini. Donc, à moins que vous n'essayiez simplement de comprendre ce qui se passe dans les coulisses ou d'utiliser des types de POD, je vous conseille de travailler avec std :: variant à la place.


3 commentaires

Concernant "Cela devrait fonctionner.": Que se passe-t-il lorsque c est actif lorsque l'objet est détruit?


@eerorika: Oh, c'est vrai. Je suppose que vous devrez appeler explicitement le destructeur de membres, mais je ne suis pas sûr, donc je préfère ne pas mettre à jour ma réponse. En supposant que vous ayez Data_Set2 x; , je suppose que vous devez appeler x.c. ~ basic_string (); (?). Je vais essayer de faire d'autres recherches à ce sujet.


Je pense que la réponse est: le destructeur de Data_Set2 doit s'assurer que w.c n'est jamais actif lors de la destruction.