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?
Cette FAQ concerne les agrégats et les POD et couvre les éléments suivants:
Réponses:
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:
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 class
fait 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 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é 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.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 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
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 ?)
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 memcpy
le contenu de votre objet dans un tableau de caractères ou de caractères non signés, puis que memcpy
le 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_cast
partir A*
de T*
et obtenir le pointeur sur le premier membre et vice - versa.
La liste se rallonge de plus en plus…
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.
private:
le cas échéant): struct A { int const a; };
alors A()
est bien formé, même si A
la définition par défaut du constructeur serait mal formée.
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é?
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.
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.
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:
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.
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 memcpy
et 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
};
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
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;
Nous pouvons nous référer au projet de norme C ++ 14 pour référence.
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.
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.
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.
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
, private
ou protected
. Vous ne pouvez pas avoir un peu public
et 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 public
vs.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 private
toute 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 Derived
doit être cette classe (car elle a un Base
comme membre). Il Base
doit donc être vide (de données). Et si Base
est vide, ainsi qu'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, pas de leur this
pointeur.
Encore une fois: pas de grosse perte.
static_cast<Base*>(&d)
et &d.b
sont 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.
Derived
doit être cette classe?
Derived
le 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.
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_aggregate
classe 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é?
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
struct D // not an aggregate
{
int i = 0;
D() = default;
explicit D(D const&) = default;
};
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:
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 , DR1734Classes à 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:
—Fin 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
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:
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.
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.
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
explicit
ou 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 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
n'é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, B
est 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 A
depuis {}
, 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, B
cesse à 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 B
constructeur de, et cet extrait devient bien formé.
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 emplace
tentera 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.
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).