web-dev-qa-db-fra.com

Initialisation d'une union avec un constructeur non trivial

J'ai une structure que je crée un constructeur personnalisé pour initialiser les membres à 0. J'ai vu dans des compilateurs plus anciens qu'en mode release, sans faire un memset à 0, les valeurs ne sont pas initialisées.

Je veux maintenant utiliser cette structure dans une union, mais j'obtiens des erreurs car elle a un constructeur non trivial.

Donc, question 1. Le constructeur implémenté par le compilateur par défaut garantit-il que tous les membres d'une structure seront initialisés à zéro? Le constructeur non trivial fait juste un memset de tous les membres à '0' pour assurer une structure propre.

Question 2: Si un constructeur doit être spécifié sur la structure de base, comment une union peut-elle être implémentée pour contenir cet élément et garantir un élément de base initialisé à 0?

53
Superpolock

Question 1: les constructeurs par défaut initialisent les membres POD à 0 selon la norme C++. Voir le texte cité ci-dessous.

Question 2: Si un constructeur doit être spécifié dans une classe de base, alors cette classe ne peut pas faire partie d'une union.

Enfin, vous pouvez fournir un constructeur pour votre syndicat:

union U 
{
   A a;
   B b;

   U() { memset( this, 0, sizeof( U ) ); }
};

Pour Q1:

À partir de C++ 03, 12.1 Constructors, pg 190

Le constructeur par défaut défini implicitement effectue l'ensemble des initialisations de la classe qui seraient effectuées par un constructeur par défaut écrit par l'utilisateur pour cette classe avec une liste d'initialiseurs mem vide (12.6.2) et un corps de fonction vide.

De C++ 03, 8.5 Initialiseurs, p. 145

Initialiser par défaut un objet de type T signifie:

  • si T est un type de classe non POD (article 9), le constructeur par défaut pour T est appelé (et l'initialisation est incorrecte si T n'a pas de constructeur par défaut accessible);
  • si T est un type de tableau, chaque élément est initialisé par défaut;
  • sinon, l'objet est initialisé à zéro .

Initialiser à zéro un objet de type T signifie:

  • si T est un type scalaire (3.9), l'objet est mis à la valeur de 0 (zéro) convertie en T;
  • si T est un type de classe non-union, chaque membre de données non statique et chaque sous-objet de classe de base est initialisé à zéro ;
  • si T est un type d'union, le premier membre de données nommé de l'objet est initialisé à zéro;
  • si T est un type de tableau, chaque élément est initialisé à zéro;
  • si T est un type de référence, aucune initialisation n'est effectuée.

Pour le T2:

À partir de C++ 03, 12.1 Constructors, pg 190

Un constructeur est trivial s'il s'agit d'un constructeur par défaut déclaré implicitement et si:

  • sa classe n'a aucune fonction virtuelle (10.3) et aucune classe de base virtuelle (10.1), et
  • toutes les classes de base directes de sa classe ont des constructeurs triviaux, et
  • pour tous les membres de données non statiques de sa classe qui sont de type classe (ou tableau de ceux-ci), chacune de ces classes a un constructeur trivial

À partir de C++ 03, 9.5 Unions, p. 162

Une union peut avoir des fonctions membres (y compris des constructeurs et des destructeurs), mais pas de fonctions virtuelles (10.3). Un syndicat ne doit pas avoir de classes de base. Une union ne doit pas être utilisée comme classe de base.Un objet d'une classe avec un constructeur non trivial (12.1), un constructeur de copie non trivial (12.8), un destructeur non trivial (12.4) ou un non trivial L'opérateur d'affectation de copie (13.5.3, 12.8) ne peut pas être membre d'une union, pas plus qu'un tableau de ces objets

Les choses ont changé pour le mieux en C++ 11.

Vous pouvez maintenant le faire légalement, comme décrit par Stroustrup lui-même (j'ai atteint ce lien depuis le article Wikipedia sur C++ 11 ).

L'exemple sur Wikipédia est le suivant:

#include <new> // Required for placement 'new'.

struct Point {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};

union U {
    int z;
    double w;
    Point p; // Illegal in C++03; legal in C++11.
    U() {new(&p) Point();} // Due to the Point member, a constructor
                           // definition is now *required*.
};

Stroustrup va dans un peu plus de détails.

31
dan-man

Les membres du syndicat AFAIK ne peuvent pas avoir de constructeurs ou de destructeurs.

Question 1: non, il n'y a pas de telle garantie. Tout membre POD ne figurant pas dans la liste d'initialisation du constructeur est initialisé par défaut, mais c'est avec un constructeur que vous définissez et possède une liste d'initialisation. Si vous ne définissez pas de constructeur, ou si vous définissez un constructeur sans liste d'initialisation et corps vide, les membres POD ne seront pas initialisés.

Les membres non POD seront toujours construits via leur constructeur par défaut qui, s'il était synthétisé, n'initialiserait pas non plus les membres POD. Étant donné que les membres d'union peuvent ne pas avoir de constructeurs, vous seriez à peu près assuré que les membres POD des structures dans une union ne seront pas initialisés.

Question 2: vous pouvez toujours initialiser des structures/unions comme ceci:

struct foo
{
    int a;
    int b;
};

union bar
{
    int a;
    foo f;
};

bar b = { 0 };
3
unwesen

Comme mentionné dans le commentaire de Greg Rogers à nwesen , vous pouvez donner à votre syndicat un constructeur (et un destructeur si vous le souhaitez):

struct foo
{
    int a;
    int b;
};

union bar
{
    bar() { memset(this, 0, sizeof(*this)); }

    int a;
    foo f;
};
2
Adam Rosenfield

Pouvez-vous faire quelque chose comme ça?

class Outer
{
public:
    Outer()
    {
        memset(&inner_, 0, sizeof(inner_));
    }
private:
    union Inner
    {
        int qty_;
        double price_;
    } inner_;
};

... ou peut-être quelque chose comme ça?

union MyUnion
{
    int qty_;
    double price_;
};

void someFunction()
{
    MyUnion u = {0};
}
0
John Dibling