web-dev-qa-db-fra.com

Que sont les agrégats et les POD et comment / pourquoi sont-ils spéciaux?

Ce FAQ concerne les agrégats et les POD et couvre les matériaux suivants:

  • Que sont les agrégats ?
  • Que sont POD s (Plain Old Data)?
  • Comment sont-ils liés?
  • Comment et pourquoi sont-ils spéciaux?
  • Quels changements pour C++ 11?
515
Armen Tsirunyan

Comment lire:

Cet article est plutôt long. Si vous souhaitez en savoir plus sur les agrégats et les POD (Plain Old Data), prenez le temps de le lire. Si vous ne vous intéressez qu'aux agrégats, lisez uniquement la première partie. Si vous ne vous intéressez qu'aux POD, vous devez d'abord lire la définition, les implications et des exemples d'agrégats, puis vous peut ​​passer aux POD, mais je recommanderais quand même de lire la première partie dans son intégralité. La notion d'agrégats est essentielle pour définir les POD. Si vous trouvez des erreurs (même mineures, y compris grammaire, stylistique, formatage, syntaxe, etc.), laissez un commentaire, je les éditerai.

Cette réponse s'applique à C++ 03. Pour d'autres normes C++, voir:

Quels sont les agrégats et pourquoi ils sont spéciaux

Définition formelle du standard C++ (C++ 03 8.5.1§1) :

Un agrégat est un tableau ou une classe (clause 9) sans constructeurs déclarés par l'utilisateur (12.1), aucun membre de données privé ou protégé non statique (clause 11), aucune classe de base (clause 10) et aucune fonction virtuelle (10.3). ).

Alors, d'accord, analysons cette définition. Tout d’abord, tout tableau est un agrégat. Une classe peut aussi être un agrégat si… attendez! rien n'est dit sur les structures ou les unions, ne peuvent-ils pas être des agrégats? Oui, ils peuvent. En C++, le terme class désigne toutes les classes, structures et unions. Ainsi, une classe (ou struct, ou union) est un agrégat si et seulement si elle répond aux critères des définitions ci-dessus. Qu'est-ce que ces critères impliquent?

  • Cela ne signifie pas qu'une classe d'agrégat ne peut pas avoir de constructeur, mais en fait, elle peut avoir un constructeur par défaut et/ou un constructeur de copie tant qu'elles sont implicitement déclarées par le compilateur et non explicitement par l'utilisateur.

  • Non privé ou protégé membres de données non statiques. Vous pouvez avoir autant de fonctions membres privées et protégées (mais pas de constructeurs) que autant de données privées ou protégées static les membres et les membres fonctionnent comme vous le souhaitez et ne violent pas les règles des classes d'agrégats

  • Une classe d'agrégat peut avoir un opérateur et/ou un destructeur d'affectation de copie déclaré/défini par l'utilisateur

  • Un tableau est un agrégat même s'il s'agit d'un tableau de type de classe non agrégé.

Voyons maintenant quelques exemples:

class NotAggregate1
{
  virtual void f() {} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static 
};

class NotAggregate3
{
public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
private:
  void f() {} // ok, just a private function
};

Vous avez eu l'idée. Voyons maintenant à quel point les agrégats sont spéciaux. Contrairement aux classes non agrégées, elles peuvent être initialisées avec des accolades {}. Cette syntaxe d'initialisation est généralement connue pour les tableaux, et nous venons d'apprendre qu'il s'agit d'agrégats. Alors commençons par eux.

Type array_name[n] = {a1, a2, …, am};

if (m == n)
le ith élément du tableau est initialisé avec unje
sinon si (m <n)
les m premiers éléments du tableau sont initialisés avec un1, une2, …, unem et les autres n - m éléments sont, si possible, valeur-initialisée (voir ci-dessous l'explication du terme)
sinon si (m> n)
le compilateur émettra une erreur
else(C'est le cas lorsque n n'est pas spécifié du tout comme int a[] = {1, 2, 3};)
la taille du tableau (n) est supposée être égale à m, donc int a[] = {1, 2, 3}; est équivalent à int a[3] = {1, 2, 3};

Lorsqu'un objet de type scalaire (bool, int, char, double, pointeurs, etc.) est valeur-initialisée cela signifie est initialisé avec 0 pour ce type (false pour bool, 0.0 pour double, etc.). Lorsqu'un objet de type classe avec un constructeur par défaut déclaré par l'utilisateur est initialisé par valeur, son constructeur par défaut est appelé. Si le constructeur par défaut est défini implicitement, tous les membres non statiques sont initialisés de manière récursive. Cette définition est imprécise et un peu incorrecte, mais elle devrait vous donner l’idée de base. Une référence ne peut pas être initialisée par une valeur. L'initialisation des valeurs pour une classe non agrégée peut échouer si, par exemple, la classe n'a pas de constructeur par défaut approprié.

Exemples d'initialisation de tableau:

class A
{
public:
  A(int) {} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}

Voyons maintenant comment les classes d'agrégats peuvent être initialisées avec des accolades. À peu près de la même façon. Au lieu des éléments du tableau, nous initialiserons les membres de données non statiques dans l'ordre d'apparition dans la définition de la classe (ils sont tous publics par définition). S'il y a moins d'initialisateurs que de membres, les autres sont initialisés par valeur. S'il est impossible d'initialiser un des membres qui n'a pas été explicitement initialisé, nous obtenons une erreur lors de la compilation. S'il y a plus d'initialiseurs que nécessaire, nous obtenons également une erreur de compilation.

struct X
{
  int i1;
  int i2;
};
struct Y
{
  char c;
  X x;
  int i[2];
  float f; 
protected:
  static double d;
private:
  void g(){}      
}; 

Y y = {'a', {10, 20}, {20, 30}};

Dans l'exemple ci-dessus, y.c est initialisé avec 'a', y.x.i1 avec 10, y.x.i2 avec 20, y.i[0] avec 20, y.i[1] avec 30 et y.f est initialisé en valeur, c'est-à-dire initialisé avec 0.0. Le membre statique protégé d n'est pas du tout initialisé, car il s'agit de static.

Les unions agrégées sont différentes en ce que vous ne pouvez initialiser que leur premier membre avec des accolades. Je pense que si vous êtes assez avancé en C++ pour envisager même d'utiliser des unions (leur utilisation peut être très dangereuse et qu'il faut y penser avec précaution), vous pouvez rechercher vous-même les règles relatives aux unions dans la norme :).

Maintenant que nous savons ce qui est spécial dans les agrégats, essayons de comprendre les restrictions sur les classes; c'est pourquoi ils sont là. Nous devons comprendre que l’initialisation avec accolades par les membres implique que la classe n’est rien d’autre que la somme de ses membres. Si un constructeur défini par l'utilisateur est présent, cela signifie que l'utilisateur doit effectuer un travail supplémentaire pour initialiser les membres. Par conséquent, l'initialisation de l'accolade serait incorrecte. Si des fonctions virtuelles sont présentes, cela signifie que les objets de cette classe ont (sur la plupart des implémentations) un pointeur sur la vtable de la classe, définie dans le constructeur, l'initialisation par accolade serait donc insuffisante. Vous pouvez déterminer le reste des restrictions de la même manière qu’un exercice :).

Assez parlé des agrégats. Maintenant, nous pouvons définir un ensemble plus strict de types, à savoir, les POD

Quels sont les POD et pourquoi ils sont spéciaux

Définition formelle du standard C++ (C++ 03 9 §4) :

Une structure POD est une classe agrégée qui ne possède pas de données membres non statiques de type non-POD-struct, non-POD-union (ou tableau de ce type) ou de référence, et n'a pas d'opérateur d'affectation de copie défini par l'utilisateur ni de destructeur défini par l'utilisateur. De même, une union POD est une union agrégée qui ne possède aucun membre de données non statique de type non-POD-struct, non-POD-union (ou tableau de ce type) ou de référence, et n'a pas d'opérateur d'affectation de copie défini par l'utilisateur. et pas de destructeur défini par l'utilisateur. Une classe POD est une classe qui est une structure POD ou une union POD.

Wow, celui-ci est plus difficile à analyser, n'est-ce pas? :) Laissons les syndicats de côté (pour les mêmes raisons que ci-dessus) et reformulons un peu plus clairement:

Une classe d'agrégat est appelée POD si elle n'a ni opérateur d'affectation de copie ni destructeur définis par l'utilisateur et si aucun de ses membres non statiques n'est une classe non-POD, un tableau de non-POD ou une référence.

Qu'est-ce que cette définition implique? (Ai-je mentionné POD signifie données anciennes lisses?)

  • Toutes les classes de POD sont des agrégats, ou, pour le dire autrement, si une classe n'est pas un agrégat, il est certain que ce n'est pas un POD.
  • Les classes, tout comme les structures, peuvent être des POD même si le terme standard est POD-struct dans les deux cas.
  • Comme dans le cas des agrégats, peu importe les membres statiques de la classe

Exemples:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1() {} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};

Les classes POD, les unions POD, les types scalaires et les tableaux de ce type sont appelés collectivement types POD.
Les POD sont spéciaux à bien des égards. Je ne donnerai que quelques exemples.

  • Les classes POD sont les plus proches de C structs. Contrairement à eux, les POD peuvent avoir des fonctions membres et des membres statiques arbitraires, mais aucun de ces deux ne modifie la disposition de la mémoire de l'objet. Donc, si vous voulez écrire une bibliothèque dynamique plus ou moins portable pouvant être utilisée à partir de C et même de .NET, vous devriez essayer de faire en sorte que toutes vos fonctions exportées prennent et renvoient uniquement des paramètres de type POD.

  • La durée de vie des objets de type classe non-POD commence lorsque le constructeur est terminé et se termine lorsque le destructeur est terminé. Pour les classes POD, la durée de vie commence lorsque le stockage de l'objet est occupé et se termine lorsque ce stockage est libéré ou réutilisé.

  • Pour les objets de types POD, il est garanti par la norme que lorsque vous memcpy le contenu de votre objet dans un tableau de caractères ou non signé, puis memcpy le contenu dans votre objet, l'objet conserver sa valeur d'origine. Notez que cette garantie n’existe pas pour les objets de types autres que POD. En outre, vous pouvez copier en toute sécurité des objets POD avec memcpy. L'exemple suivant suppose que T est un type de POD:

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
    
  • déclaration goto. Comme vous le savez peut-être, il est illégal (le compilateur doit émettre une erreur) d'effectuer un saut via goto à partir d'un point où une variable n'était pas encore dans la portée à un point où elle le serait déjà. Cette restriction ne s'applique que si la variable est de type non-POD. Dans l'exemple suivant, f() est mal formé alors que g() est bien formé. Notez que le compilateur de Microsoft est trop libéral avec cette règle: il envoie simplement un avertissement dans les deux cas.

    int f()
    {
      struct NonPOD {NonPOD() {}};
      goto label;
      NonPOD x;
    label:
      return 0;
    }
    
    int g()
    {
      struct POD {int i; char c;};
      goto label;
      POD x;
    label:
      return 0;
    }
    
  • Il est garanti qu’il n’y aura pas de bourrage au début d’un objet POD. En d'autres termes, si le premier membre d'un POD-classe A est de type T, vous pouvez en toute sécurité reinterpret_cast de A* à T* et obtenir le pointeur sur le premier membre, et inversement.

La liste se rallonge de plus en plus…

Conclusion

Il est important de comprendre ce qu'est exactement un POD car de nombreuses fonctionnalités linguistiques, comme vous le voyez, se comportent différemment pour elles.

531
Armen Tsirunyan

Quels changements pour C++ 11?

Agrégats

La définition standard d'un agrégat a légèrement changé, mais elle reste pratiquement la même:

Un agrégat est un tableau ou une classe (clause 9) sans constructeurs fournis par l'utilisateur (12.1), no accolades ou égales-initialiseurs pour les membres de données non statiques (9.2), ni privé ni membres de données non statiques protégés (clause 11), pas de classes de base (clause 10), ni fonctions virtuelles (10.3).

Ok, qu'est-ce qui a changé?

  1. Auparavant, un agrégat pouvait ne comporter aucun constructeur déclaré par l'utilisateur, mais à présent, il ne peut pas comporter de constructeur fourni par l'utilisateur. Y a-t-il une différence? Oui, car vous pouvez maintenant déclarer des constructeurs et par défaut eux:

    struct Aggregate {
        Aggregate() = default; // asks the compiler to generate the default implementation
    };
    

    Il s'agit toujours d'un agrégat, car un constructeur (ou toute fonction membre spéciale) défini par défaut sur la première déclaration n'est pas fourni par l'utilisateur.

  2. Désormais, un agrégat ne peut avoir aucun accolade-ou-égal-initialiseurs pour les membres de données non statiques. Qu'est-ce que ça veut dire? Eh bien, c'est simplement parce qu'avec cette nouvelle norme, nous pouvons initialiser les membres directement dans la classe comme ceci:

    struct NotAggregate {
        int x = 5; // valid in C++11
        std::vector<int> s{1,2,3}; // also valid
    };
    

    L'utilisation de cette fonctionnalité ne fait plus de la classe un agrégat, car elle équivaut en gros à fournir votre propre constructeur par défaut.

Donc, ce qui est un agrégat n'a pas beaucoup changé. C'est toujours la même idée de base, adaptée aux nouvelles fonctionnalités.

Qu'en est-il des POD?

Les POD ont subi beaucoup de changements. Beaucoup de règles précédentes concernant les POD ont été assouplies dans cette nouvelle norme et la définition de la définition donnée dans la norme a été radicalement modifiée.

L'idée d'un POD est de capturer fondamentalement deux propriétés distinctes:

  1. Il supporte l'initialisation statique, et
  2. La compilation d’un POD en C++ vous donne la même structure de mémoire qu’une structure compilée en C.

Pour cette raison, la définition a été scindée en deux concepts distincts: trivial classes et standard-layout classes, car elles sont plus utiles que POD. La norme utilise maintenant rarement le terme POD, préférant les concepts plus spécifiques trivial et mise en page standard.

La nouvelle définition indique en gros qu'un POD est une classe à la fois triviale et standard, et que cette propriété doit être conservée de manière récursive pour tous les membres de données non statiques:

Une structure POD est une classe non-union qui est à la fois une classe triviale et une classe à disposition standard. Elle ne possède aucun membre de données non statique de type structure non-POD, union non-POD (ou tableau de ce type). De même, une union POD est une union qui est à la fois une classe triviale et une classe de présentation standard et ne contient aucun membre de données non statique de type structure non-POD, union non-POD (ou tableau de ce type). Une classe POD est une classe qui est soit une structure POD, soit une union POD.

Examinons chacune de ces deux propriétés en détail séparément.

Classes triviales

Trivial est la première propriété mentionnée ci-dessus: les classes triviales supportent l'initialisation statique. Si une classe est trivialement copiable (un sur-ensemble de classes triviales), il est correct de copier sa représentation sur l'endroit avec des éléments tels que memcpy et de s'attendre à ce que le résultat soit identique.

La norme définit une classe triviale comme suit:

Une classe trivialement copiable est une classe qui:

- n'a pas de constructeur de copie non trivial (12.8),

- n'a pas de constructeur de mouvement non-trivial (12.8),

- n'a pas d'opérateur d'affectation de copie non trivial (13.5.3, 12.8),

- n'a pas d'opérateur d'affectation de mouvement non-trivial (13.5.3, 12.8), et

- possède un destructeur trivial (12.4).

Une classe triviale est une classe qui possède un constructeur trivial par défaut (12.1) et qui peut être copiée de manière triviale.

[Remarque: En particulier, une classe trivialement copiable ou triviale n'a pas de fonctions virtuelles ni de classes de base virtuelles .- end note]

Alors, quelles sont toutes ces choses triviales et non triviales?

Un constructeur de copie/déplacement pour la classe X est trivial s’il n’est pas fourni par l’utilisateur et si

- la classe X n'a ​​pas de fonctions virtuelles (10.3) ni de classes de base virtuelles (10.1), et

- le constructeur sélectionné pour copier/déplacer chaque sous-objet de classe de base directe est trivial, et

- pour chaque membre de données non statique de X qui est de type classe (ou un tableau de celui-ci), le constructeur sélectionné pour copier/déplacer ce membre est trivial;

sinon, le constructeur de copie/déplacement n'est pas trivial.

En gros, cela signifie qu'un constructeur de copie ou de déplacement est trivial s'il n'est pas fourni par l'utilisateur, si la classe n'a rien de virtuel et cette propriété est valable de manière récursive pour tous les membres de la classe et pour la classe de base.

La définition d'un opérateur d'assignation de copie/déplacement trivial est très similaire, remplaçant simplement le mot "constructeur" par "opérateur d'assignation".

Un destructeur trivial a également une définition similaire, avec la contrainte supplémentaire qu’il ne peut pas être virtuel.

Et une autre règle similaire existe pour les constructeurs par défaut triviaux, avec l’ajout qu’un constructeur par défaut n’est pas trivial si la classe a des membres de données non statiques avec accolade-ou-égal-initialiseurs, ce que nous ' J'ai vu ci-dessus.

Voici quelques exemples pour tout éclaircir:

// empty classes are trivial
struct Trivial1 {};

// all special members are implicit
struct Trivial2 {
    int x;
};

struct Trivial3 : Trivial2 { // base class is trivial
    Trivial3() = default; // not a user-provided ctor
    int y;
};

struct Trivial4 {
public:
    int a;
private: // no restrictions on access modifiers
    int b;
};

struct Trivial5 {
    Trivial1 a;
    Trivial2 b;
    Trivial3 c;
    Trivial4 d;
};

struct Trivial6 {
    Trivial2 a[23];
};

struct Trivial7 {
    Trivial6 c;
    void f(); // it's okay to have non-virtual functions
};

struct Trivial8 {
     int x;
     static NonTrivial1 y; // no restrictions on static members
};

struct Trivial9 {
     Trivial9() = default; // not user-provided
      // a regular constructor is okay because we still have default ctor
     Trivial9(int x) : x(x) {};
     int x;
};

struct NonTrivial1 : Trivial3 {
    virtual void f(); // virtual members make non-trivial ctors
};

struct NonTrivial2 {
    NonTrivial2() : z(42) {} // user-provided ctor
    int z;
};

struct NonTrivial3 {
    NonTrivial3(); // user-provided ctor
    int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
                                      // still counts as user-provided
struct NonTrivial5 {
    virtual ~NonTrivial5(); // virtual destructors are not trivial
};

Disposition standard

Standard-layout est la deuxième propriété. La norme mentionne que celles-ci sont utiles pour communiquer avec d'autres langues, car une classe de présentation standard a la même disposition de mémoire que la structure ou l'union C équivalente.

C'est une autre propriété qui doit être vérifiée de manière récursive pour les membres et toutes les classes de base. Et comme d'habitude, aucune fonction virtuelle ou classe de base virtuelle n'est autorisée. Cela rendrait la mise en page incompatible avec C.

Une règle assouplie ici est que les classes de disposition standard doivent avoir tous les membres de données non statiques avec le même contrôle d'accès. Auparavant, ils devaient être tous public, mais vous pouvez maintenant les rendre privés ou protégés, tant qu'ils sont tous privés ou tous protégés.

Lors de l'utilisation de l'héritage, n seul la classe de l'ensemble de l'arborescence d'héritage peut avoir des membres de données non statiques et le premier membre de données non statique ne peut pas être d'un type de classe de base (cela pourrait enfreindre les règles d'alias), sinon, ce n'est pas une classe de mise en page standard.

Voici comment la définition va dans le texte standard:

Une classe de mise en page standard est une classe qui:

- ne possède pas de données membres non statiques de type classe de présentation non standard (ou tableau de ce type) ou référence,

- n'a pas de fonctions virtuelles (10.3) ni de classes de base virtuelles (10.1),

- a le même contrôle d'accès (article 11) pour tous les membres de données non statiques,

- n'a pas de classes de base de mise en page non standard,

- n'a pas de données membres non statiques dans la classe la plus dérivée et au plus une classe de base avec des données membres non statiques, ou n'a pas de classes de base avec des données membres non statiques, et

- n'a pas de classes de base du même type que le premier membre de données non statique.

Une structure de présentation standard est une classe de présentation standard définie avec la structure class-key ou la classe class-key.

Une union de présentation standard est une classe de présentation standard définie avec l'union classe-clé.

[Remarque: Les classes de format standard sont utiles pour communiquer avec du code écrit dans d'autres langages de programmation. Leur disposition est spécifiée en 9.2 .- end note]

Et voyons quelques exemples.

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's ok
    int x;
    int y;
};

struct StandardLayout4 : StandardLayout1 {
    int x;
    int y;

    void f(); // perfectly fine to have non-virtual functions
};

struct StandardLayout5 : StandardLayout1 {
    int x;
    StandardLayout1 y; // can have members of base type if they're not the first
};

struct StandardLayout6 : StandardLayout1, StandardLayout5 {
    // can use multiple inheritance as long only
    // one class in the hierarchy has non-static data members
};

struct StandardLayout7 {
    int x;
    int y;
    StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};

struct StandardLayout8 {
public:
    StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
    int x;
};

struct StandardLayout9 {
    int x;
    static NonStandardLayout1 y; // no restrictions on static members
};

struct NonStandardLayout1 {
    virtual f(); // cannot have virtual functions
};

struct NonStandardLayout2 {
    NonStandardLayout1 X; // has non-standard-layout member
};

struct NonStandardLayout3 : StandardLayout1 {
    StandardLayout1 x; // first member cannot be of the same type as base
};

struct NonStandardLayout4 : StandardLayout3 {
    int z; // more than one class has non-static data members
};

struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class

Conclusion

Avec ces nouvelles règles, beaucoup plus de types peuvent être des POD maintenant. Et même si un type n'est pas POD, nous pouvons tirer parti de certaines propriétés de POD séparément (s'il ne s'agit que d'une présentation triviale ou standard).

La bibliothèque standard a des traits pour tester ces propriétés dans l'en-tête <type_traits>:

template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;
431

Ce qui a changé pour C++ 14

Nous pouvons nous référer au Projet de norme C++ 14 pour référence.

Agrégats

Ceci est couvert dans la section 8.5.1 Agrégats qui nous donne la définition suivante:

Un agrégat est un tableau ou une classe (clause 9) sans constructeurs fournis par l'utilisateur (12.1), aucun membre de données privé ou protégé non statique (clause 11), aucune classe de base (clause 10) et aucune fonction virtuelle (10.3). ).

La seule modification qui consiste maintenant à ajouter des initialiseurs de membre de classe ne rend pas une classe non agrégée. Ainsi, l'exemple suivant tiré de initialisation d'agrégat C++ 11 pour les classes avec des initialiseurs de membre à cadence :

struct A
{
  int a = 3;
  int b = 3;
};

n'était pas un agrégat en C++ 11 mais bien en C++ 14. Cette modification est couverte dans N3605: initialiseurs de membres et agrégats , dont l'abrégé est le suivant:

Bjarne Stroustrup et Richard Smith ont soulevé une question concernant l'initialisation d'agrégats et le fait que les initialiseurs de membres ne fonctionnent pas ensemble. Ce document propose de résoudre le problème en adoptant le libellé proposé par Smith qui supprime une restriction selon laquelle les agrégats ne peuvent pas avoir d'initialiseurs de membres.

POD reste le même

La définition de la structure POD () de données anciennes ) est traitée dans la section 9 Classes qui dit:

Une structure POD110 est une classe non-union qui est à la fois une classe triviale et une classe à disposition standard, et ne possède aucun membre de données non statique de type structure non-POD, union non-POD (ou tableau de ce type). De même, une union POD est une union qui est à la fois une classe triviale et une classe à disposition standard et ne comporte aucun membre de données non statique de type structure non-POD, union non-POD (ou tableau de ce type). Une classe POD est une classe qui est soit une structure POD, soit une union POD.

qui est la même formulation que C++ 11.

Modifications de mise en page standard pour C++ 14

Comme indiqué dans les commentaires , le module s'appuie sur la définition de standard-layout et cela a changé pour C++ 14, mais c’est via des rapports de défaut qui ont été appliqués à C++ 14 après coup.

Il y avait trois DR:

Donc standard-layout est passé de ce pré C++ 14:

Une classe de mise en page standard est une classe qui:

  • (7.1) n'a pas de données membres non statiques de type classe de présentation non standard (ou tableau de ce type) ou référence,
  • (7.2) n'a pas de fonctions virtuelles ([class.virtual]) et pas de classes de base virtuelles ([class.mi]),
  • (7.3) a le même contrôle d’accès (Clause [class.access]) pour tous les membres de données non statiques,
  • (7.4) n'a pas de classes de base de mise en page non standard,
  • (7.5) n'a pas de données membres non statiques dans la classe la plus dérivée et au plus une classe de base avec des données membres non statiques, ou n'a pas de classes de base avec des données membres non statiques, et
  • (7.6) n'a pas de classes de base du même type que le premier membre de données non statique.109

Pour ceci en C++ 14 :

Une classe S est une classe de mise en page standard si:

  • (3.1) n'a pas de données membres non statiques de type classe de présentation non standard (ou tableau de ce type) ni référence,
  • (3.2) n'a pas de fonctions virtuelles ni de classes de base virtuelles,
  • (3.3) a le même contrôle d'accès pour tous les membres de données non statiques,
  • (3.4) n'a pas de classes de base de mise en page non standard,
  • (3.5) a au plus un sous-objet de classe de base d'un type donné,
  • (3.6) a tous les membres de données non statiques et les champs de bits de la classe et ses classes de base déclarés pour la première fois dans la même classe, et
  • (3.7) ne contient aucun élément de l'ensemble M(S) des types en tant que classe de base, où, pour tout type X, M(X) est défini comme suit.104 [Note : M(X) est l'ensemble des types de tous les sous-objets n'appartenant pas à la classe de base pouvant être décalés à zéro dans X. - note de fin]
    • (3.7.1) Si X est un type de classe non-union sans membres de données non statiques (éventuellement hérités), l'ensemble M(X) est vide.
    • (3.7.2) Si X est un type de classe non-union avec un membre de données non statique de type X0 de taille zéro ou le premier membre de données non statique de X (où ce membre peut être une union anonyme. ), l’ensemble M(X) est composé de X0 et des éléments de M (X0).
    • (3.7.3) Si X est un type d'union, l'ensemble M(X) est l'union de tous les M(Ui) et de l'ensemble contenant tous les Ui, où chaque Ui est le type du ième membre de données non statique de X.
    • (3.7.4) Si X est un type de tableau avec le type d'élément Xe, l'ensemble M(X) se compose de Xe et des éléments de M (Xe).
    • (3.7.5) Si X est un type non-class, ni array, l'ensemble M(X) est vide.
96
Shafik Yaghmour

pouvez-vous s'il vous plaît élaborer les règles suivantes:

J'essaierai:

a) les classes de présentation standard doivent avoir toutes les données membres non statiques avec le même contrôle d'accès

C'est simple: tous les membres de données non statiques doivent tous être public, private ou protected. Vous ne pouvez pas avoir de public et d'autres private.

Leur raisonnement s'appuie sur le raisonnement voulant qu'il y ait une distinction entre "mise en page standard" et "mise en page non standard". À savoir, donner au compilateur la liberté de choisir comment mettre les choses en mémoire. Il ne s'agit pas que de pointeurs vtable.

À l'époque où ils normalisaient le C++ en 98, ils devaient prévoir comment les utilisateurs le mettraient en œuvre. Bien qu'ils aient acquis une certaine expérience en matière d'implémentation de différentes versions de C++, ils n'étaient pas certains de ce qui se passait. Ils ont donc décidé de faire preuve de prudence: laissez aux compilateurs autant de liberté que possible.

C'est pourquoi la définition de POD en C++ 98 est si stricte. Cela donnait aux compilateurs C++ une grande latitude sur la disposition des membres pour la plupart des classes. Fondamentalement, les types de POD étaient destinés à être des cas spéciaux, quelque chose que vous avez spécifiquement écrit pour une raison.

Quand on travaillait sur C++ 11, ils avaient beaucoup plus d'expérience avec les compilateurs. Et ils ont compris que ... Les rédacteurs de compilateurs C++ sont vraiment paresseux. Ils avaient toute cette liberté, mais ils ne faisaient rien avec.

Les règles de la mise en page standard codifient plus ou moins la pratique courante: la plupart des compilateurs n’ont pas vraiment à changer grand-chose, voire rien du tout pour les mettre en œuvre (à part peut-être quelques éléments pour les caractères de type correspondants).

Maintenant, en ce qui concerne public/private, les choses sont différentes. La liberté de réorganiser les membres qui sont public vs. private peut réellement compter pour le compilateur, en particulier pour les versions de débogage. De plus, étant donné que la disposition standard est compatible avec d’autres langues, la disposition ne doit pas être différente dans le processus de mise au point ou de publication.

Ensuite, il y a le fait que cela ne fait pas vraiment mal à l'utilisateur. Si vous créez une classe encapsulée, il y a de fortes chances que tous vos membres de données soient de toute façon private. En règle générale, vous n'exposez pas les membres de données publics sur des types entièrement encapsulés. Cela ne poserait donc problème que pour les quelques utilisateurs qui le souhaitent, qui souhaitent cette division.

Donc, ce n'est pas une grosse perte.

b) une seule classe de l'ensemble de l'héritage peut avoir des données membres non statiques,

La raison en est la raison pour laquelle ils ont à nouveau normalisé la présentation standard: pratique courante.

Il y a non pratique courante le fait d'avoir deux membres d'un arbre d'héritage qui stockent des objets. Certains mettent la classe de base avant la dérivée, d'autres le font dans l'autre sens. De quelle manière commandez-vous les membres s’ils viennent de deux classes de base? Etc. Les compilateurs divergent grandement sur ces questions.

De plus, grâce à la règle zéro/un/infini, une fois que vous dites que vous pouvez avoir deux classes avec des membres, vous pouvez en dire autant que vous le souhaitez. Cela nécessite l'ajout de nombreuses règles de mise en page pour savoir comment gérer cela. Vous devez dire comment fonctionne l'héritage multiple, les classes qui placent leurs données avant les autres classes, etc. C'est beaucoup de règles, pour un gain matériel minime.

Vous ne pouvez pas créer tout ce qui n’a pas de fonctions virtuelles et une présentation standard par défaut du constructeur.

et le premier membre de données non statique ne peut pas être d'un type de classe de base (cela pourrait enfreindre les règles de crénelage).

Je ne peux pas vraiment parler à celui-ci. Je ne suis pas assez au courant des règles de repliement de C++ pour vraiment le comprendre. Mais cela a quelque chose à voir avec le fait que le membre de base partagera la même adresse que la classe de base elle-même. C'est:

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;

Et c'est probablement contre les règles de crénelage de C++. En quelque sorte.

Cependant, considérez ceci: quelle serait l’utilité d’avoir la possibilité de le faire ? Etant donné qu'une seule classe peut avoir des données membres non statiques, alors Derived doit être cette classe (puisqu'elle a un Base en tant que membre). Donc, Base doit être vide (de données). Et si Base est vide, ainsi que une classe de base ... pourquoi en avoir un membre de données?

Puisque Base est vide, il n'a pas d'état. Ainsi, toutes les fonctions membres non statiques feront ce qu'elles font en fonction de leurs paramètres, et non de leur pointeur this.

Encore une fois: pas de grosse perte.

44
Nicol Bolas

Changements dans C++ 17

Téléchargez le brouillon final de la norme internationale C++ 17 ici .

Agrégats

C++ 17 développe et améliore les agrégats et leur initialisation. La bibliothèque standard inclut également désormais une classe de traits de type std::is_aggregate. Voici la définition formelle des sections 11.6.1.1 et 11.6.1.2 (références internes supprimées):

Un agrégat est un tableau ou une classe avec
- aucun constructeur fourni par l'utilisateur, explicite ou hérité,
- aucun membre de données privé ou protégé non statique,
- pas de fonctions virtuelles, et
- pas de classes de base virtuelles, privées ou protégées.
[Remarque: L'initialisation d'agrégat ne permet pas d'accéder aux membres ni aux constructeurs des classes de base protégées et privées. —Fin note]
Les éléments d'un agrégat sont:
- pour un tableau, les éléments du tableau en ordre croissant d'indice, ou
- pour une classe, les classes de base directes dans l'ordre de la déclaration, suivies des membres de données non statiques directs qui ne sont pas membres d'une union anonyme, dans l'ordre de la déclaration.

Qu'est ce qui a changé?

  1. Les agrégats peuvent maintenant avoir des classes de base publiques et non virtuelles. De plus, il n'est pas nécessaire que les classes de base soient des agrégats. S'ils ne sont pas agrégés, ils sont initialisés par liste.
struct B1 // not a aggregate
{
    int i1;
    B1(int a) : i1(a) { }
};
struct B2
{
    int i2;
    B2() = default;
};
struct M // not an aggregate
{
    int m;
    M(int a) : m(a) { }
};
struct C : B1, B2
{
    int j;
    M m;
    C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Les constructeurs par défaut explicites sont interdits
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Les constructeurs héréditaires sont interdits
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Classes triviales

La définition de classe trivial a été retravaillée en C++ 17 pour traiter plusieurs défauts qui n'étaient pas corrigés en C++ 14. Les changements étaient de nature technique. Voici la nouvelle définition à 12.0.6 (références internes éliées):

Une classe trivialement copiable est une classe:
- où chaque constructeur de copie, constructeur de déplacement, opérateur d'affectation de copie et opérateur d'affectation de déplacement est supprimé ou trivial,
- comportant au moins un constructeur de copie non supprimé, un constructeur de déplacement, un opérateur d'affectation de copie ou un opérateur d'affectation de déplacement, et
- qui a un destructeur trivial, non supprimé.
Une classe triviale est une classe qui est trivialement copiable et qui possède un ou plusieurs constructeurs par défaut, qui sont tous triviaux ou supprimés et dont au moins un n'est pas supprimé. [Remarque: en particulier, une classe trivialement copiable ou triviale n'a pas de fonctions virtuelles ni de classes de base virtuelles. — note finale]

Changements:

  1. Sous C++ 14, pour qu'une classe soit triviale, elle ne pouvait pas avoir d'opérateur de copie/déplacement constructeur/affectation non trivial. Cependant, alors un constructeur/opérateur implicite déclaré par défaut pourrait ne pas être trivial et pourtant défini comme supprimé car, par exemple, la classe contenait un sous-objet de type classe qui ne pouvait pas être copié/déplacé. La présence d'un tel constructeur/opérateur non trivial, défini comme supprimé, ferait en sorte que toute la classe ne soit pas triviale. Un problème similaire existait avec les destructeurs. C++ 17 clarifie le fait que la présence de tels constructeurs/opérateurs ne rend pas la classe non copiable, donc non triviale, et qu'une classe copiable trivial doit avoir un destructeur trivial, non supprimé. DR1734 , DR1928
  2. C++ 14 permettait à une classe trivialement copiable, et donc à une classe triviale, de déclarer chaque opérateur de copie/déplacement/opérateur/assignation supprimé. Si telle que classe était aussi une disposition standard, elle pourrait cependant être légalement copiée/déplacée avec std::memcpy. C’était une contradiction sémantique, car, en définissant comme supprimés tous les opérateurs de constructeur/d’assignation, le créateur de la classe voulait clairement que la classe ne puisse pas être copiée/déplacée, alors que la classe répondait toujours à la définition d’une classe trivialement copiable. Par conséquent, dans C++ 17, nous avons une nouvelle clause stipulant qu'une classe trivialement copiable doit comporter au moins un constructeur/un opérateur de copie/déplacement trivial, non supprimé (mais pas nécessairement accessible au public). Voir N4148 , DR1734
  3. La troisième modification technique concerne un problème similaire avec les constructeurs par défaut. Sous C++ 14, une classe peut avoir des constructeurs par défaut triviaux définis implicitement comme supprimés, tout en restant une classe triviale. La nouvelle définition précise qu'une classe triviale doit avoir au moins un constructeur par défaut trivial et non supprimé. Voir DR1496

Classes à disposition standard

La définition de la mise en page standard a également été retravaillée pour traiter les rapports de défauts. Encore une fois, les changements étaient de nature technique. Voici le texte de la norme (12.0.7). Comme auparavant, les références internes sont éliminées:

Une classe S est une classe de mise en page standard si:
- ne contient aucun membre de données non statique de type classe de présentation non standard (ou tableau de ce type) ni référence,
- n'a pas de fonctions virtuelles ni de classes de base virtuelles,
- a le même contrôle d'accès pour tous les membres de données non statiques,
- n'a pas de classes de base de mise en page non standard,
- a au plus un sous-objet de classe de base d'un type donné,
- tous les membres de données et champs de bits non statiques de la classe et ses classes de base sont d'abord déclarés dans la même classe, et
- ne contient aucun élément de l'ensemble M(S) des types (définis ci-dessous) en tant que classe de base.108
M (X) est défini comme suit:
- Si X est un type de classe non-union sans membres de données non statiques (éventuellement hérités), l'ensemble M(X) est vide.
- Si X est un type de classe non-union dont le premier membre de données non statique a le type X0 (où ce membre peut être une union anonyme), l'ensemble M(X) consiste en X0 et les éléments de M (X0).
- Si X est un type d'union, l'ensemble M(X) est l'union de tous les M(Ui) et de l'ensemble contenant tous les Ui, où chaque Ui est le type du ième membre de données non statique de X.
- Si X est un type de tableau avec le type d'élément Xe, l'ensemble M(X) se compose de Xe et des éléments de M (Xe).
- Si X n'est pas un type de classe ni de type tableau, l'ensemble M(X) est vide.
[Remarque: M(X) est l'ensemble des types de tous les sous-objets n'appartenant pas à la classe de base garantis dans une classe à présentation standard à un décalage d'origine dans X. —Fin note]
[ Exemple:

struct B { int i; }; // standard-layout class
struct C : B { }; // standard-layout class
struct D : C { }; // standard-layout class
struct E : D { char : 4; }; // not a standard-layout class
struct Q {};
struct S : Q { };
struct T : Q { };
struct U : S, T { }; // not a standard-layout class
- fin exemple]
108) Cela garantit que deux sous-objets ayant le même type de classe et appartenant au même objet le plus dérivé ne sont pas alloués à la même adresse.

Changements:

  1. Clarification sur le fait que l'exigence selon laquelle une seule classe de l'arbre de dérivation "possède" des membres de données non statiques fait référence à une classe où ces membres de données sont d'abord déclarés, et non aux classes où ils peuvent être hérités, et a étendu cette exigence aux champs de bits non statiques. . Également précisé qu'une classe de mise en page standard "a au plus un sous-objet de classe de base d'un type donné". Voir DR181 , DR1881
  2. La définition de standard-layout n'a jamais permis que le type d'une classe de base soit du même type que le premier membre de données non statique. C'est pour éviter une situation où un membre de données au décalage zéro a le même type que n'importe quelle classe de base. La norme C++ 17 fournit une définition plus récursive et récursive de "l'ensemble des types de tous les sous-objets n'appartenant pas à la classe de base garantis dans une classe de mise en page standard à un décalage zéro" de manière à interdire ces types d'être le type de n'importe quelle classe de base. Voir DR1672 , DR212 .

Remarque: Le comité des normes C++ souhaitait que les modifications ci-dessus basées sur les rapports d'incident s'appliquent au C++ 14, bien que le nouveau langage ne soit pas dans le C publié. ++ 14 standard. C'est dans la norme C++ 17.

33
ThomasMcLeod

Qu'est-ce qui va changer pour C++ 20?

Comme il est encore tôt, une partie de cette réponse pourrait changer dans le futur. Après le reste du thème clair de cette question, la signification et l’utilisation des agrégats continuent à changer avec chaque norme. Il y a plusieurs changements clés à l'horizon.

Types avec constructeurs déclarés par l'utilisateur P1008

En C++ 17, ce type est toujours un agrégat:

struct X {
    X() = delete;
};

Et par conséquent, X{} est toujours compilé car il s'agit d'une initialisation d'agrégat - et non d'une invocation de constructeur. Voir aussi: Quand un constructeur privé n'est-il pas un constructeur privé?

En C++ 20, la restriction ne nécessitera plus:

aucun constructeur fourni par l'utilisateur, explicit, ni hérité

à

aucun constructeur déclaré ou hérité par l'utilisateur

Ceci a été adopté dans le projet de travail C++ 2 . Ni la X ici ni la C dans la question liée ne seront des agrégats en C++ 20.

Cela crée également un effet yo-yo avec l'exemple suivant:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

En C++ 11/14, B était pas un agrégat en raison de la classe de base, de sorte que B{} exécute valeur- initialisation qui appelle B::B() qui appelle A::A(), en un point accessible. C'était bien formé.

En C++ 17, B est devenu un agrégat parce que les classes de base étaient autorisées, ce qui a rendu B{} initialisation d'agrégat. Cela nécessite une copie de liste-initialisation d'une A à partir de {}, mais en dehors du contexte de B, où elle n'est pas accessible. En C++ 17, c'est mal formé (auto x = B(); serait bien).

En C++ 20 maintenant, à cause du changement de règle ci-dessus, B cesse à nouveau d'être un agrégat (pas à cause de la classe de base, mais à cause du constructeur par défaut déclaré par l'utilisateur, même s'il est utilisé par défaut). Nous revenons donc à travers le constructeur de B, et cet extrait devient bien formé.

Initialisation des agrégats à partir d’une liste de valeurs entre parenthèses P96

Un problème courant qui se pose est celui de l’utilisation des constructeurs de style emplace()- avec des agrégats:

struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error

Cela ne fonctionne pas car emplace essaiera de réaliser efficacement l'initialisation X(1, 2) qui n'est pas valide. La solution typique consiste à ajouter un constructeur à X, mais avec cette proposition (qui fait actuellement son chemin dans Core), les agrégats auront effectivement des constructeurs synthétisés qui agissent comme il convient - et se comportent comme des constructeurs normaux. Le code ci-dessus sera compilé tel quel en C++ 20 (en supposant que cette fonctionnalité soit approuvée, ce qui semble probable).

Déduction des arguments de modèle de classe (CTAD) pour les agrégats P1021

En C++ 17, cela ne compile pas:

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

Les utilisateurs doivent rédiger leur propre guide de déduction pour tous les modèles globaux:

template <typename T> Point(T, T) -> Point<T>;

Mais comme ceci est en quelque sorte "la chose évidente" à faire, et qu’il n’est fondamentalement que du bricolage, le langage le fera pour vous. Ce changement ayant été approuvé par Evolution en novembre 2018, l'exemple ci-dessus sera probablement compilé en C++ 20 (sans avoir besoin du guide de déduction fourni par l'utilisateur).

8
Barry