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

548

Cette FAQ concerne les agrégats et les POD et couvre les éléments suivants:

  • Que sont les agrégats ?
  • Que sont les POD (Plain Old Data)?
  • Comment sont-ils liés?
  • Comment et pourquoi sont-ils spéciaux?
  • Quels changements pour C ++ 11?
Armen Tsirunyan
la source
1
Qu'est-ce que le sous-ensemble POD: stackoverflow.com/questions/146452/what-are-pod-types-in-c
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Peut-on dire que la motivation derrière ces définitions est à peu près: POD == memcpy'able, Aggregate == agrégat-initialisable?
Ofek Shilon

Réponses:

572

Comment lire:

Cet article est assez long. Si vous voulez en savoir plus sur les agrégats et les POD (Plain Old Data), prenez le temps de le lire. Si vous êtes uniquement intéressé par les agrégats, lisez uniquement la première partie. Si vous n'êtes intéressé que par les POD, vous devez d'abord lire la définition, les implications et les exemples d'agrégats, puis vous pouvez passer aux POD, mais je recommanderais toujours 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 la grammaire, la stylistique, le formatage, la syntaxe, etc.) veuillez laisser un commentaire, je vais le modifier.

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

Que 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 (article 9) sans constructeurs déclarés par l'utilisateur (12.1), sans membres de données non statiques privés ou protégés (article 11), sans classes de base (article 10) et sans fonctions virtuelles (10.3 ).

Alors, OK, 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-elles pas être des agrégats? Oui, ils peuvent. En C ++, le terme classfait référence à toutes les classes, structures et unions. Ainsi, une classe (ou struct, ou union) est un agrégat si et seulement si elle satisfait aux critères des définitions ci-dessus. Qu'impliquent ces critères?

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

  • Aucun membre de données non statique privé ou protégé . Vous pouvez avoir autant de fonctions membres privées et protégées (mais pas de constructeurs) ainsi que autant de membres de données statiques privés ou protégés et de fonctions membres que vous le souhaitez et ne pas enfreindre les règles pour les classes agrégées

  • Une classe agrégée 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 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 en quoi 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 communément connue pour les tableaux, et nous venons d'apprendre qu'il s'agit d'agrégats. Commençons donc avec eux.

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

si (m == n)
le i ème élément du tableau est initialisé avec un i
sinon si (m <n)
les premiers m éléments du tableau sont initialisés avec un 1 , un 2 ,…, un m et les autresn - méléments sont, si possible, initialisés en valeur (voir ci-dessous pour l'explication du terme)
sinon si (m> n)
le compilateur émettra une erreur
sinon (c'est le cas lorsque n n'est pas spécifié du tout comme int a[] = {1, 2, 3};)
la taille de le tableau (n) est supposé être égal à m,int a[] = {1, 2, 3};estdoncéquivalent àint a[3] = {1, 2, 3};

Quand un objet de type scalaire ( bool, int, char, double, pointeurs, etc.) est la valeur initialisée cela signifie qu'il est initialisé avec 0pour ce type ( falsepour bool, 0.0pour double, etc.). Lorsqu'un objet de type classe avec un constructeur par défaut déclaré par l'utilisateur est initialisé en valeur, son constructeur par défaut est appelé. Si le constructeur par défaut est implicitement défini, tous les membres non statiques sont récursivement initialisés en valeur. 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 en valeur. L'initialisation de la valeur d'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 agrégées peuvent être initialisées avec des accolades. À peu près de la même manière. Au lieu des éléments du tableau, nous initialiserons les membres de données non statiques dans l'ordre de leur apparition dans la définition de classe (ils sont tous publics par définition). S'il y a moins d'initialiseurs que de membres, les autres sont initialisés en valeur. S'il est impossible d'initialiser la valeur d'un des membres qui n'ont pas été explicitement initialisés, nous obtenons une erreur au moment 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.cest initialisé avec 'a', y.x.i1avec 10, y.x.i2avec 20, y.i[0]avec 20, y.i[1]avec 30et y.fest initialisé en valeur, c'est-à-dire initialisé avec 0.0. Le membre statique protégé dn'est pas du tout initialisé, car il l'est static.

Les unions agrégées sont différentes en ce sens que vous ne pouvez initialiser que leur premier membre avec des accolades. Je pense que si vous êtes suffisamment avancé en C ++ pour même envisager d'utiliser des unions (leur utilisation peut être très dangereuse et doit être réfléchie attentivement), vous pouvez rechercher vous-même les règles pour les unions dans la norme :).

Maintenant que nous savons ce qui est spécial au sujet des agrégats, essayons de comprendre les restrictions sur les classes; c'est pourquoi ils sont là. Nous devons comprendre que l'initialisation des accolades par membre implique que la classe n'est rien de plus 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, donc 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 vers la soi-disant vtable de la classe, qui est définie dans le constructeur, donc l'initialisation de l'accolade serait insuffisante. Vous pouvez comprendre 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

Que sont les POD et pourquoi ils sont spéciaux

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

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

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

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

Qu'implique cette définition? (Ai-je mentionné POD signifie Plain Old Data ?)

  • Toutes les classes POD sont des agrégats, ou, pour le dire autrement, si une classe n'est pas un agrégat, ce n'est certainement 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
  • Tout 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 ces types sont collectivement appelés types POD.
Les POD sont spéciaux à bien des égards. Je vais vous donner quelques exemples.

  • Les classes POD sont les plus proches des structures C. 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 qui peut ê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 retournent uniquement les paramètres des types POD.

  • La durée de vie des objets de type non POD commence à la fin du constructeur et se termine à la fin du destructeur. 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 mettez memcpyle contenu de votre objet dans un tableau de caractères ou de caractères non signés, puis que memcpyle contenu retourne dans votre objet, l'objet conserve sa valeur d'origine. Notez qu'il n'y a pas de telle garantie pour les objets de types non-POD. Vous pouvez également copier des objets POD en toute sécurité avec memcpy. L'exemple suivant suppose que T est de type 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
  • instruction goto. Comme vous le savez peut-être, il est illégal (le compilateur devrait émettre une erreur) de faire un saut via goto d'un point où une variable n'était pas encore dans la portée à un point où elle est déjà dans la portée. Cette restriction s'applique uniquement si la variable est de type non POD. Dans l'exemple suivant, il f()est mal formé alors qu'il g()est bien formé. Notez que le compilateur de Microsoft est trop libéral avec cette règle - il émet 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 remplissage au début d'un objet POD. En d' autres termes, si un premier élément de POD classe A est de type T, vous pouvez en toute sécurité à reinterpret_castpartir A*de T*et obtenir le pointeur sur le premier membre et vice - versa.

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 de langage, comme vous le voyez, se comportent différemment pour elles.

Armen Tsirunyan
la source
3
Bonne réponse. Commentaires: "Si le constructeur par défaut est implicitement défini, alors tous les membres non statiques sont récursivement initialisés en valeur." et "L'initialisation de la valeur pour une classe non agrégée peut échouer si, par exemple, la classe n'a pas de constructeur par défaut approprié." n'est pas correct: l'initialisation de la valeur d'une classe avec un constructeur par défaut déclaré implicitement ne nécessite pas un constructeur par défaut défini implicitement. Ainsi, étant donné (insérer private:le cas échéant): struct A { int const a; };alors A()est bien formé, même si Ala définition par défaut du constructeur serait mal formée.
Johannes Schaub - litb
4
@Kev: Si vous parvenez à regrouper les mêmes informations dans une réponse plus courte, nous serions tous ravis de voter pour!
sbi
3
@Armen note également que vous pouvez faire plusieurs réponses à la même question. Chaque réponse pourrait contenir une partie de la solution à la question. Visser cette chose de marque acceptée, à mon avis :)
Johannes Schaub - litb
3
La réponse est excellente. Je revois encore ce post depuis quelques temps. À propos des avertissements pour Visual Studio. "instruction goto" pour pod vient avec ignorance du compilateur MSVC comme vous l'avez mentionné. Mais pour l'instruction switch / case, il génère une erreur de compilation. Sur la base de ce concept, j'ai créé un test-pod-checker: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/…
bruziuz
2
Dans la puce qui commence par "La durée de vie des objets de type non POD commence à la fin du constructeur et se termine à la fin du destructeur". la dernière partie devrait plutôt indiquer "quand le destructeur démarre".
Quokka
458

Quels changements pour C ++ 11?

Agrégats

La définition standard d'un agrégat a légèrement changé, mais elle est à peu près la même:

Un agrégat est un tableau ou une classe (article 9) sans constructeur fourni par l'utilisateur (12.1), sans initialiseur d' accolade ou égal pour les membres de données non statiques (9.2), sans membres de données non statiques privés ou protégés ( Article 11), aucune classe de base (article 10) et aucune fonction virtuelle (10.3).

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

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

    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) 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 initialiseur d' accolade ou égal 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é fait que la classe n'est plus un agrégat car elle équivaut à fournir votre propre constructeur par défaut.

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

Et les POD?

Les POD ont subi de nombreux changements. Beaucoup de règles précédentes sur les POD ont été assouplies dans cette nouvelle norme, et la façon dont la définition est fournie dans la norme a été radicalement modifiée.

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

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

Pour cette raison, la définition a été divisée en deux concepts distincts: les classes triviales et les classes à disposition standard , car elles sont plus utiles que POD. La norme utilise désormais rarement le terme POD, préférant les concepts triviaux et de mise en page standard plus spécifiques .

La nouvelle définition dit essentiellement qu'un POD est une classe à la fois triviale et à présentation standard, et cette propriété doit être 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 de mise en page standard, et n'a pas de membres de données non statiques de type struct non-POD, union non-POD (ou tableau de ces types). De même, une union POD est une union qui est à la fois une classe triviale et une classe de disposition standard, et n'a pas de membres de données non statiques de type structure non-POD, union non-POD (ou tableau de ces types). Une classe POD est une classe qui est soit une structure POD, soit une union POD.

Passons en revue 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 prennent en charge l'initialisation statique. Si une classe est trivialement copiable (un surensemble de classes triviales), il est correct de copier sa représentation sur place avec des choses comme memcpyet s'attendre à ce que le résultat soit le même.

La norme définit une classe triviale comme suit:

Une classe trivialement copiable est une classe qui:

- n'a pas de constructeurs de copie non triviaux (12.8),

- n'a pas de constructeurs de mouvements non triviaux (12.8),

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

- n'a pas d'opérateurs d'affectation de déplacement non triviaux (13.5.3, 12.8), et

- possède un destructeur trivial (12.4).

Une classe triviale est une classe qui a un constructeur par défaut trivial (12.1) et qui est trivialement copiable.

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

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

Un constructeur copier / déplacer pour la classe X est trivial s'il n'est pas fourni par l'utilisateur et si

- la classe X n'a ​​aucune fonction virtuelle (10.3) et aucune classe de base virtuelle (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 tableau de celui-ci), le constructeur sélectionné pour copier / déplacer ce membre est trivial;

sinon, le constructeur copier / déplacer n'est pas trivial.

Fondamentalement, cela signifie qu'un constructeur de copie ou de déplacement est trivial s'il n'est pas fourni par l'utilisateur, la classe ne contient rien de virtuel et cette propriété est récursive pour tous les membres de la classe et pour la classe de base.

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

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

Et encore 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 des accolades ou des initialiseurs égaux , comme nous l'avons vu ci-dessus.

Voici quelques exemples pour tout clarifier:

// 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

La disposition standard est la deuxième propriété. La norme mentionne que ceux-ci sont utiles pour communiquer avec d'autres langages, et c'est parce qu'une classe de disposition standard a la même disposition de mémoire que la structure ou l'union C équivalente.

Il s'agit d'une autre propriété qui doit être conservée récursivement 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 disposition incompatible avec C.

Une règle plus souple ici est que les classes de mise en page 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 publics , mais maintenant vous pouvez 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, une seule classe dans l'ensemble de l'arbre d'héritage peut avoir des membres de données non statiques, et le premier membre de données non statiques ne peut pas être d'un type de classe de base (cela pourrait enfreindre les règles d'alias), sinon, ce n'est pas un standard - classe de mise en page.

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

Une classe à présentation standard est une classe qui:

- n'a pas de membres de données non statiques de type classe non standard (ou tableau de ces types) ou référence,

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

- dispose du 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 à disposition non standard,

- soit n'a pas de membres de données non statiques dans la classe la plus dérivée et au plus une classe de base avec des membres de données non statiques, soit n'a pas de classes de base avec des membres de données 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 à disposition standard est une classe à disposition standard définie avec la structure de clé de classe ou la classe de clé de classe.

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

[ Remarque: les classes à présentation standard sont utiles pour communiquer avec du code écrit dans d'autres langages de programmation. Leur disposition est spécifiée en 9.2. —Fin 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 désormais être des POD. Et même si un type n'est pas POD, nous pouvons tirer parti de certaines des propriétés POD séparément (s'il ne s'agit que d'une disposition 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;
R. Martinho Fernandes
la source
2
pouvez-vous s'il vous plaît élaborer les règles suivantes: a) les classes de mise en page standard doivent avoir tous les membres de données non statiques avec le même contrôle d'accès; b) une seule classe dans l'ensemble de l'héritage peut avoir des membres de données non statiques, et le premier membre de données non statiques ne peut pas être d'un type de classe de base (cela pourrait enfreindre les règles d'alias). Surtout quelles en sont les raisons? Pour la règle ultérieure, pouvez-vous fournir un exemple de rupture d'alias?
Andriy Tylychko,
@AndyT: Voir ma réponse. J'ai essayé de répondre au mieux de mes connaissances.
Nicol Bolas
5
Pourrait vouloir mettre à jour cela pour C ++ 14, ce qui a supprimé l'exigence "pas d'accolade ni d'initialisation égale" pour les agrégats.
TC
@TC merci pour l'avertissement. Je vais bientôt rechercher ces modifications et les mettre à jour.
R. Martinho Fernandes
1
Concernant l'aliasing: il existe une règle de mise en page C ++ selon laquelle si une classe C a une base X (vide) et que le premier membre de données de C est de type X, ce premier membre ne peut pas avoir le même décalage que la base X; il obtient un octet de remplissage factice devant lui, si nécessaire pour éviter cela. Avoir deux instances de X (ou sous-classe) à la même adresse pourrait casser des choses qui doivent distinguer différentes instances via leurs adresses (une instance vide n'a rien d'autre pour la distinguer ...). Dans tous les cas, la nécessité de mettre cet octet de remplissage rompt la «disposition compatible».
greggo
106

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 (article 9) sans constructeurs fournis par l'utilisateur (12.1), aucun membre de données non statique privé ou protégé (article 11), aucune classe de base (article 10) et aucune fonction virtuelle (10.3 ).

Le seul changement est maintenant l'ajout d' initialiseurs de membres de classe ne fait pas d'une classe un non-agrégat. Ainsi, l'exemple suivant de l' initialisation agrégée C ++ 11 pour les classes avec des initialiseurs de rythme dans les membres :

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

n'était pas un agrégat en C ++ 11 mais il l'est en C ++ 14. Cette modification est couverte par N3605: initialiseurs de membres et agrégats , qui contient le résumé suivant:

Bjarne Stroustrup et Richard Smith ont soulevé un problème concernant l'initialisation globale et les initialiseurs de membres ne fonctionnant pas ensemble. Cet article 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'initialisateurs de membres.

POD reste le même

La définition de la structure POD ( plain old data ) est traitée dans la section 9 Classes qui dit:

A POD struct 110 est une classe non-union qui est à la fois une classe triviale et une classe de mise en page standard, et n'a pas de membres de données non statiques de type struct non-POD, union non-POD (ou tableau de ces types). De même, une union POD est une union qui est à la fois une classe triviale et une classe de mise en page standard, et n'a pas de membres de données non statiques de type struct non-POD, union non-POD (ou tableau de ces types). 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 pod repose sur la définition de la mise en page standard et cela a changé pour C ++ 14, mais cela s'est fait via des rapports de défauts qui ont été appliqués à C ++ 14 après coup.

Il y avait trois DR:

La mise en page standard est donc passée de ce Pre C ++ 14:

Une classe à présentation standard est une classe qui:

  • (7.1) n'a pas de membres de données non statiques de type classe de disposition non standard (ou tableau de ces types) 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) possède 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 à disposition non standard,
  • (7.5) n'a pas de membres de données non statiques dans la classe la plus dérivée et au plus une classe de base avec des membres de données non statiques, ou n'a pas de classes de base avec des membres de données 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 cela en C ++ 14 :

Une classe S est une classe à présentation standard si:

  • (3.1) n'a pas de membres de données non statiques de type classe non standard (ou tableau de ces types) ou 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 à disposition non standard,
  • (3.5) possède au plus un sous-objet de classe de base de tout type donné,
  • (3.6) a tous les membres de données et champs de bits non statiques de la classe et ses classes de base déclarées pour la première fois dans la même classe, et
  • (3.7) n'a pas d'élément de l'ensemble M (S) de types comme classe de base, où pour tout type X, M (X) est défini comme suit.104 [Remarque: M (X) est l'ensemble des types de tous les sous-objets non-classe de base qui peuvent être à un décalage nul dans X. - note de fin]
    • (3.7.1) Si X est un type de classe non-union sans aucun membre de données non statique (éventuellement hérité), 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 qui est soit de taille nulle, soit le premier membre de données non statique de X (où ledit 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 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-classe, non-tableau, l'ensemble M (X) est vide.
Shafik Yaghmour
la source
4
Il y a une proposition pour permettre aux agrégats d'avoir une classe de base tant qu'elle est constructible par défaut voir N4404
Shafik Yaghmour
alors que POD peut rester le même, C ++ 14 StandardLayoutType, qui est une exigence pour POD, a changé selon cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 merci, je ne sais pas comment j'ai raté ça, je vais essayer de mettre à jour au cours des prochains jours.
Shafik Yaghmour
Faites-moi savoir si vous pouvez trouver un exemple qui est POD en C ++ 14 mais pas en C ++ 11 :-) J'ai commencé une liste détaillée d'exemples à: stackoverflow.com/questions/146452/what- are-pod-types-in-c /…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 donc ce qui s'est passé ici est que si nous regardons la description de mise en page standard en C ++ 11 et C ++ 14, ils correspondent. Ces modifications étaient appliquées via des rapports de défauts en C ++ 14. Donc, quand j'ai écrit cela à l'origine, c'était correct :-p
Shafik Yaghmour
47

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

J'essaierai:

a) les classes de mise en page standard doivent avoir tous les membres de données 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, privateou protected. Vous ne pouvez pas avoir un peu publicet certainsprivate .

Le raisonnement pour eux va au raisonnement pour avoir une distinction entre "disposition standard" et "disposition non standard" du tout. À savoir, pour donner au compilateur la liberté de choisir comment mettre les choses en mémoire. Il ne s'agit pas seulement de pointeurs vtables.

À l'époque où ils ont normalisé le C ++ en 98, ils devaient essentiellement prédire comment les gens l'implémenteraient. Bien qu'ils aient eu une certaine expérience de la mise en œuvre avec différentes versions de C ++, ils n'étaient pas certains des choses. Ils ont donc décidé d'être prudents: donner aux compilateurs autant de liberté que possible.

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

Lorsque C ++ 11 était en cours d'élaboration, ils avaient beaucoup plus d'expérience avec les compilateurs. Et ils ont réalisé que ... les auteurs de compilateurs C ++ sont vraiment paresseux. Ils avaient toute cette liberté, mais ils ne l'ont pas fait rien avec elle.

Les règles de mise en page standard codifient plus ou moins la pratique courante: la plupart des compilateurs n'ont pas vraiment eu à changer grand-chose pour les implémenter (à part peut-être quelques trucs pour les traits de type correspondants).

Maintenant, en ce qui concerne public/ private, les choses sont différentes. La liberté de réorganiser quels membres sont publicvs.private réalité peut être importante pour le compilateur, en particulier dans les versions de débogage. Et puisque le point de la disposition standard est qu'il existe une compatibilité avec d'autres langues, vous ne pouvez pas avoir la disposition différente dans le débogage par rapport à la version.

Ensuite, il y a le fait que cela ne blesse pas vraiment l'utilisateur. Si vous créez une classe encapsulée, les chances sont bonnes que tous vos membres de données le soient de privatetoute façon. En règle générale, vous n'exposez pas les membres de données publiques sur des types entièrement encapsulés. Ce ne serait donc un problème que pour les quelques utilisateurs qui veulent le faire, qui veulent cette division.

Ce n'est donc pas une grosse perte.

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

La raison de celui-ci revient à pourquoi ils ont normalisé à nouveau la disposition standard: pratique courante.

Il n'y a pas de pratique courante lorsqu'il s'agit d'avoir deux membres d'un arbre d'héritage qui stockent réellement des choses. Certains mettent la classe de base avant la dérivée, d'autres le font dans l'autre sens. De quelle façon commandez-vous les membres s'ils viennent de deux classes de base? Etc. Les compilateurs divergent considérablement 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, quelles classes mettent leurs données avant les autres classes, etc. C'est beaucoup de règles, pour un gain matériel très faible.

Vous ne pouvez pas créer tout ce qui n'a pas de fonctions virtuelles et une disposition standard de constructeur par défaut.

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

Je ne peux pas vraiment parler de celui-ci. Je ne connais pas assez les règles d'alias 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 contraire aux règles d'alias de C ++. En quelque sorte.

Cependant, considérez ceci: comment utile pourrait avoir la capacité de le faire jamais réellement être? Puisqu'une seule classe peut avoir des membres de données non statiques, alors Deriveddoit être cette classe (car elle a un Basecomme membre). Il Base doit donc être vide (de données). Et si Baseest vide, ainsi qu'une classe de base ... pourquoi en avoir un membre de données?

Puisque Baseest 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, pas de leur thispointeur.

Encore une fois: pas de grosse perte.

Nicol Bolas
la source
Merci pour l'explication, ça aide beaucoup. Probablement malgré static_cast<Base*>(&d)et &d.bsont du même Base*type, ils pointent vers des choses différentes, brisant ainsi la règle d'alias. S'il vous plaît corrigez-moi.
Andriy Tylychko
1
et, pourquoi si une seule classe peut avoir des membres de données non statiques, alors Deriveddoit être cette classe?
Andriy Tylychko
3
@AndyT: Pour que Derivedle premier membre de soit sa classe de base, il doit avoir deux choses: une classe de base et un membre . Et comme une seule classe de la hiérarchie peut avoir des membres (tout en ayant une disposition standard), cela signifie que sa classe de base ne peut pas avoir de membres.
Nicol Bolas
3
@AndyT, oui, vous avez essentiellement raison, IME, à propos de la règle d'alias. Deux instances distinctes du même type doivent avoir des adresses mémoire distinctes. (Cela permet le suivi de l'identité de l'objet avec des adresses mémoire.) L'objet de base et le premier membre dérivé sont des instances différentes, ils doivent donc avoir des adresses différentes, ce qui oblige à ajouter un remplissage, ce qui affecte la disposition de la classe. S'ils étaient de types différents, cela n'aurait pas d'importance; les objets de types différents peuvent avoir la même adresse (une classe et son premier membre de données, par exemple).
Adam H. Peterson
46

Changements dans C ++ 17

Téléchargez la version finale de la norme internationale C ++ 17 ici .

Agrégats

C ++ 17 développe et améliore les agrégats et l'initialisation des agrégats. La bibliothèque standard comprend également désormais une std::is_aggregateclasse de traits de type. Voici la définition formelle des sections 11.6.1.1 et 11.6.1.2 (références internes élues):

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

Qu'est ce qui a changé?

  1. Les agrégats peuvent désormais avoir des classes de base publiques et non virtuelles. De plus, il n'est pas obligatoire que les classes de base soient des agrégats. S'ils ne sont pas des agrégats, 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érités ne sont pas autorisés
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Classes triviales

La définition de la classe triviale a été retravaillée en C ++ 17 pour corriger plusieurs défauts qui n'étaient pas traités en C ++ 14. Les changements étaient de nature technique. Voici la nouvelle définition au 12.0.6 (références internes élues):

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 soit supprimé, soit trivial,
- qui a au moins un constructeur de copie, constructeur de déplacement, opérateur d'affectation de copie non supprimé, ou déplacer l'opérateur d'affectation, et
- qui a un destructeur trivial, non supprimé.
Une classe triviale est une classe qui peut être copiée trivialement et qui a 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 de fin]

Changements:

  1. Sous C ++ 14, pour qu'une classe soit triviale, la classe ne pouvait pas avoir d'opérateurs constructeur / affectation de copie / déplacement qui n'étaient pas triviaux. Cependant, un constructeur / opérateur implicitement déclaré par défaut pourrait être non 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 que la classe entière ne serait pas triviale. Un problème similaire existait avec les destructeurs. C ++ 17 précise que la présence de tels constructeurs / opérateurs ne fait pas que la classe soit copiable de manière non triviale, donc non triviale, et qu'une classe copiable trivialement doit avoir un destructeur trivial et non supprimé. DR1734 , DR1928
  2. C ++ 14 permettait à une classe trivialement copiable, donc une classe triviale, de voir chaque constructeur / opérateur d'affectation copier / déplacer déclaré comme supprimé. Si une telle classe était également une présentation standard, elle pourrait cependant être légalement copiée / déplacée avec std::memcpy. Il s'agissait d'une contradiction sémantique, car, en définissant comme supprimés tous les opérateurs constructeur / affectation, le créateur de la classe avait clairement l'intention de ne pas copier / déplacer la classe, mais la classe répondait toujours à la définition d'une classe trivialement copiable. Par conséquent, en C ++ 17, nous avons une nouvelle clause indiquant que la classe trivialement copiable doit avoir au moins un constructeur de copie / déplacement trivial, non supprimé (mais pas nécessairement accessible au public). Voir N4148 , DR1734
  3. Le troisième changement technique concerne un problème similaire avec les constructeurs par défaut. Sous C ++ 14, une classe pouvait avoir des constructeurs par défaut triviaux qui étaient implicitement définis 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 précédemment, les références internes sont éludées:

Une classe S est une classe à disposition standard si elle:
- n'a pas de membres de données non statiques de type classe à disposition non standard (ou tableau de ces types) ou référence,
- n'a pas de fonctions virtuelles et pas 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 à présentation non standard,
- a au plus un sous-objet de classe de base de tout type donné,
- a tous les membres de données et champs de bits non statiques dans la classe et ses classes de base déclarées pour la première fois dans la même classe, et
- n'a aucun élément de l'ensemble M (S) de types (défini ci-dessous) en tant que classe de base.108 - 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.
M (X) est défini comme suit:

- Si X est un type de classe non union dont le premier membre de données non statique a le type X0 (où ledit membre peut être une union anonyme), l'ensemble M (X) se compose de X0 et des é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 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 est un type non classe, non tableau, l'ensemble M (X) est vide.
[Remarque: M (X) est l'ensemble des types de tous les sous-objets non-classe de base qui sont garantis dans une classe de mise en page standard à un décalage de zéro 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 qui ont le même type de classe et qui appartiennent au même objet le plus dérivé ne sont pas alloués à la même adresse.

Changements:

  1. Clarifié que l'exigence selon laquelle une seule classe dans l'arbre de dérivation "a" 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, pas aux classes où ils peuvent être hérités, et a étendu cette exigence aux champs de bits non statiques . A é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 DR1813 , DR1881
  2. La définition de la disposition standard n'a jamais permis au type d'une classe de base d'être le même type que le premier membre de données non statique. C'est pour éviter une situation où un membre de données à 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 rigoureuse et récursive de "l'ensemble des types de tous les sous-objets non-classe de base qui sont garantis dans une classe de mise en page standard à un décalage nul" afin d'interdire ces types d'être le type de toute classe de base. Voir DR1672 , DR2120 .

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

ThomasMcLeod
la source
Notez que je viens de mettre à jour ma réponse les défauts de modifications de mise en page standard ont le statut CD4, ce qui signifie qu'ils sont réellement appliqués à C ++ 14. C'est pourquoi ma réponse ne les a pas inclus car cela s'est produit après avoir écrit ma réponse.
Shafik Yaghmour
Remarque, j'ai commencé une prime sur cette question.
Shafik Yaghmour
Merci @ShafikYaghmour. Je vais examiner l'état des rapports de défauts et modifier ma réponse en conséquence.
ThomasMcLeod
@ShafikYaghmour, Après un examen du processus C ++ 14 et il me semble que, alors que ces DR ont été "acceptés" lors de la réunion de juin 2014 à Rapperswil, le langage de la réunion d'Issaquah de février 2014 est devenu C ++ 14. Voir isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "conformément aux règles ISO, nous n'avons officiellement approuvé aucune modification du document de travail C ++." Suis-je en train de manquer quelque chose?
ThomasMcLeod
Ils ont le statut «CD4», ce qui signifie qu'ils doivent appliquer en mode C ++ 14.
Shafik Yaghmour
14

Quels changements dans

Suivant le reste du thème clair de cette question, la signification et l'utilisation des agrégats continuent de 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 donc, X{}compile toujours parce que c'est l'initialisation agrégée - pas une invocation de constructeur. Voir aussi: Quand un constructeur privé n'est-il pas un constructeur privé?

En C ++ 20, la restriction changera d'exiger:

aucun constructeur fourni par l'utilisateur explicitou hérité

à

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

Cela a été adopté dans le projet de travail C ++ 20 . Ni leX ici ni le Cdans 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, Bn'était pas un agrégat en raison de la classe de base, B{}effectue donc l' initialisation de la valeur qui appelle B::B()quels appels A::A(), à un point où il est accessible. C'était bien formé.

En C ++ 17, Best devenu un agrégat car les classes de base étaient autorisées, ce qui a fait l' B{}initialisation de l'agrégat. Cela nécessite de copier-initialiser une liste Adepuis {}, mais en dehors du contexte de B, où elle n'est pas accessible. En C ++ 17, c'est mal formé (ce auto x = B();serait bien quand même).

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

Initialisation d'agrégats à partir d'une liste de valeurs entre parenthèses P960

Un problème commun qui se pose est de vouloir utiliser emplace()des constructeurs de style avec des agrégats:

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

Cela ne fonctionne pas, car emplacetentera d'effectuer efficacement l'initialisation X(1, 2), ce qui n'est pas valide. La solution typique consiste à ajouter un constructeur àX , mais avec cette proposition (actuellement en cours de développement via Core), les agrégats auront effectivement des constructeurs synthétisés qui font la bonne chose - et se comporteront comme des constructeurs réguliers. Le code ci-dessus sera compilé tel quel en C ++ 20.

Déduction d'arguments de modèle de classe (CTAD) pour les agrégats P1021 (spécifiquement P1816 )

En C ++ 17, cela ne compile pas:

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

Point p{1, 2}; // error

Les utilisateurs devraient rédiger leur propre guide de déduction pour tous les modèles d'agrégation:

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

Mais comme c'est en quelque sorte «la chose évidente» à faire, et qu'il s'agit simplement d'un passe-partout, le langage le fera pour vous. Cet exemple sera compilé en C ++ 20 (sans avoir besoin du guide de déduction fourni par l'utilisateur).

Barry
la source
Bien que je vote de manière positive, il semble un peu tôt d'ajouter ceci, je ne sais rien de majeur qui pourrait changer cela avant que C ++ 2x ne soit fait.
Shafik Yaghmour
@ShafikYaghmour Oui, probablement beaucoup trop tôt. Mais étant donné que SD était la date limite pour les nouvelles fonctionnalités linguistiques, ce sont les deux seuls en vol que je connaisse - le pire des cas, je viens de bloquer-supprimer une de ces sections plus tard? Je viens de voir la question active avec la prime et j'ai pensé que c'était le bon moment pour sonner avant d'oublier.
Barry
Je comprends, j'ai été tenté plusieurs fois pour des cas similaires. Je crains toujours que quelque chose de majeur change et je vais devoir le réécrire.
Shafik Yaghmour
@ShafikYaghmour Il semble que rien ne va changer ici :)
Barry
J'espère que cela est mis à jour maintenant, avec C ++ 20 déjà publié
Noone AtAll