Qu'est-ce que les agrégats et les gousses et comment/pourquoi sont-ils Spéciaux?
Ce FAQ est sur les Agrégats et les Gousses et couvre le matériel suivant:
- Qu'est-ce que agrégats ?
- Qu'est-ce que POD s (Plain Old Data)?
- comment sont-ils reliés?
- comment et pourquoi sont-ils Spéciaux?
- Quels changements pour le C++11?
5 réponses
Comment lire:
cet article est plutôt long. Si vous voulez en savoir plus sur les agrégats et les PODs (données anciennes), prenez le temps de le lire. Si vous ne vous intéressez qu'aux agrégats, ne lisez que la première partie. Si vous êtes intéressé seulement par les gousses, alors vous devez d'abord lire la définition, les implications, et les exemples d'agrégats et puis vous mai sauter aux gousses, mais je recommande toujours la lecture de la première partie dans son intégralité. Notion des agrégats est essentiel pour la définition des Gousses. Si vous trouvez des erreurs (même mineures, y compris la grammaire, la stylistique, le formatage, la syntaxe, etc.), merci de laisser un commentaire, je vais modifier.
Ce sont des agrégats et pourquoi ils sont spéciaux
définition formelle de la norme C++ ( C++03 8.5.1 §1 ) :
un agrégat est un réseau ou une classe (article 9) sans l'utilisateur déclaré constructeurs (12.1), aucun élément de données non statique privé ou protégé (clause 11)), pas de classes de base (clause 10), et pas de fonctions virtuelles (10.3).
donc, OK, analysons cette définition. Tout d'abord, un tableau est un agrégat. Une classe peut aussi être un agrégat si... attendez! rien n'est dit sur les structures ou les syndicats, ne peuvent-ils pas être des agrégats? Oui, ils le peuvent. En C++, le terme class
se réfère à toutes les classes, structures et syndicats. Ainsi, une classe (ou struct, ou union) est un agrégat si et seulement s'il répond aux critères des définitions ci-dessus. Que faire de ces critères impliquent?
-
cela ne signifie pas qu'une classe agrégée 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
-
Non privé ou protégé non-données membres statiques . Vous pouvez avoir autant de fonctions membres privées et protégées (mais pas de constructeurs) ainsi que beaucoup de fonctions membres privées ou protégées static données membres et fonctions membres comme vous le souhaitez et ne violent pas les règles pour les classes agrégées
-
une classe agrégée peut avoir un opérateur de copie-assignation déclaré/défini par l'utilisateur et / ou destructeur
-
un tableau est un agrégat même s'il s'agit d'un tableau de type de classe non agrégé.
voyons maintenant quelques exemples:
class NotAggregate1
{
virtual void f() {} //remember? no virtual functions
};
class NotAggregate2
{
int x; //x is private by default and non-static
};
class NotAggregate3
{
public:
NotAggregate3(int) {} //oops, user-defined constructor
};
class Aggregate1
{
public:
NotAggregate1 member1; //ok, public member
Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment
private:
void f() {} // ok, just a private function
};
vous comprenez. Voyons maintenant comment 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 connu pour les tableaux, et nous viens d'apprendre que ce sont des agrégats. Donc, nous allons commencer avec eux.
Type array_name[n] = {a1, a2, …, am};
si (m = = n)
l'élément i th du tableau est initialisé avec un i
autres si(m < n)
le premier m les éléments du tableau sont initialisés avec un 1 , un 2 , ..., un m et l'autre "151990920 des éléments" sont, si possible, valeur initialisée (voir ci-dessous pour l'explication du terme)
else if(m > n)
le compilateur émettra une erreur
else (c'est le cas quand n n'est pas spécifié du tout comme int a[] = {1, 2, 3};
)
la taille du tableau (n) est supposée être égale à m, donc int a[] = {1, 2, 3};
est équivalent à int a[3] = {1, 2, 3};
Lorsqu'un objet de type scalaire ( bool
, int
, char
, double
, pointeurs, etc.) est valeur initialisée cela signifie 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 valeur-initialisé, son constructeur par défaut est appelé. Si le constructeur par défaut est implicitement défini, alors tous les membres non statiques sont initialisés de façon récursive. Cette définition est imprécise et un peu inexact, mais il devrait vous donner l'idée de base. Une référence ne peut pas être initialisée par une valeur. La valeur d'initialisation pour un non-classe d'agrégation peut échouer si, par exemple, la classe a n'y a pas de constructeur par défaut.
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 bagues. Peu 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). Si il y a moins d'initialiseurs que de membres, le reste est initialisé par la valeur. S'il est impossible d'évaluer-initialiser un des membres qui n'a pas été explicitement initialisé, nous obtenons une erreur de compilation. Si il y a plus d'initialiseurs que nécessaire, on obtient 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é par '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 une valeur initialisée, c'est-à-dire initialisée avec 0.0
. L'élément statique protégé d
n'est pas initialisé du tout, car il est static
.
les unions agrégées sont différentes en ce que vous pouvez initialiser seulement leur premier membre avec accolades. Je pense que si vous êtes assez avancée en C++ d'envisager d'utiliser des syndicats (leur utilisation peut être très dangereux et doit être pensé soigneusement), vous pourriez chercher les règles pour les syndicats dans la norme vous-même :).
maintenant que nous savons ce qui est spécial au sujet des agrégats, essayons de comprendre les restrictions sur les classes; c'est-à-dire, pourquoi ils sont là. Il faut comprendre que l'initialisation memberwise avec des accolades implique que la classe n'est rien de plus que la somme de ses membres. Si un utilisateur défini par le constructeur est présent, cela signifie que l'utilisateur doit faire un travail supplémentaire pour initialiser les membres par conséquent, l'initialisation de brace 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 le soi-disant vtable de la classe, qui est défini dans le constructeur, donc brace-initialisation serait insuffisante. Vous pourriez comprendre le reste de la restriction d'une manière semblable à un exercice :).
assez parlé des agrégats. Maintenant nous pouvons définir un ensemble plus strict de types, à savoir, les gousses
Quels sont les Gousses et pourquoi ils sont spéciaux
définition formelle de la norme C++ ( C++03 9 §4 ) :
une pod-struct est une classe agrégée qui n'a pas de membres de données non statiques type non-POD-struct, non-POD-union (or tableau de ces types) ou de référence, et n'a pas d'assignation de copie définie par l'utilisateur opérateur et non défini par l'utilisateur destructeur. De même, une POD-union est une union agrégée qui n'a pas de données non statiques membres du type non-POD-struct, non-POD-union (ou tableau de ces types) ou de référence, et n'a pas d'assignation de copie définie par l'utilisateur opérateur et non défini par l'utilisateur destructeur. Une classe de gousse est une classe c'est soit un pod-struct ou un POD-union.
Wow, celui-ci est plus dur à analyser, n'est-ce pas? :) Laissons les syndicats de côté (pour les mêmes raisons que ci-dessus) et reformulons un peu plus clairement chemin:
une classe agrégée est appelée une gousse si il n'a pas de copie-assignation définie par l'utilisateur opérateur et destructeur et aucun ses membres non statiques est un non-POD la classe, le tableau de la non-POD, ou un référence.
que signifie 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, alors ce n'est certainement pas un POD
- les Classes, tout comme les structures ,peuvent être PODs même si le terme standard est POD-struct pour les deux cas
- comme dans le cas des agrégats, peu importe les membres statiques de la classe
exemples:
struct POD
{
int x;
char y;
void f() {} //no harm if there's a function
static std::vector<char> v; //static members do not matter
};
struct AggregateButNotPOD1
{
int x;
~AggregateButNotPOD1() {} //user-defined destructor
};
struct AggregateButNotPOD2
{
AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};
POD-classes, Les pod-unions, les types scalaires et les matrices de tels types sont collectivement appelés POD-types.
Les gousses sont spéciales à bien des égards. Je vais vous donner juste quelques exemples.
-
POD-classes sont les plus proches de C des structures. Contrairement à eux, les PODs peuvent avoir des fonctions de membre et des membres statiques arbitraires, mais ni l'un ni l'autre de ces deux ne changent 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é à partir de C et même.Net, vous devriez essayer de faire toutes vos fonctions exportées prendre et retourner Seulement les paramètres de type POD.
-
la durée de vie des objets de type non-POD commence lorsque le constructeur a terminé et se termine lorsque le destructeur a terminé. Pour les classes POD, la durée de vie commence lorsque le stockage de l'objet est occupé et se termine lorsque le stockage est libéré ou réutilisé.
-
pour les objets de type POD il est garanti par la norme que lorsque vous
memcpy
le contenu de votre objet dans un tableau de char ou char non signé, et puismemcpy
le contenu de nouveau dans votre objet, l'objet tiendra sa valeur originale. Notez qu'il n'y a pas de garantie pour les objets de type non-POD. En outre, vous pouvez copier en toute sécurité des objets POD avecmemcpy
. L'exemple suivant suppose que T est un type de gousse:#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 en scope à un point où elle est déjà en scope. Cette restriction ne s'applique que si la variable est de type non-POD. Dans l'exemple suivant
f()
est mal formé, alors queg()
est bien formé. Notez que le compilateur de Microsoft est trop libéral avec cette règle-il juste 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 rembourrage au début d'un objet POD. En d'autres termes, si le premier membre D'un POD-class A est de type T, vous pouvez en toute sécurité
reinterpret_cast
deA*
àT*
et obtenir le pointeur vers le premier membre et vice versa.
La liste continue encore et encore...
Conclusion
il est important de comprendre ce qu'est exactement une gousse parce que de nombreuses caractéristiques linguistiques, comme vous le voyez, se comportent différemment pour elles.
Quels changements pour le C++11?
agrégats
la définition standard d'un agrégat a légèrement changé, mais elle est toujours à peu près la même:
un agrégat est un tableau ou une classe (Clause 9) sans constructeur fourni par l'utilisateur (12.1), pas de corset ou égal initialiseurs pour les non-membres de données statiques (9.2), no private ou protected éléments de données non statiques (article 11 )), pas de classes de base (Clause 10), et pas de fonctions virtuelles (10.3).
Ok, qu'est-ce qui a changé?
-
auparavant, un agrégat ne pouvait pas avoir de constructeurs déclarés par l'utilisateur , mais maintenant il ne peut pas avoir de constructeurs fournis par l'utilisateur . Est-il une différence? Oui, il y en a, parce que maintenant vous pouvez déclarer les constructeurs et par défaut eux:
struct Aggregate { Aggregate() = default; // asks the compiler to generate the default implementation };
il s'agit toujours d'un agrégat parce qu'un constructeur (ou toute fonction membre spéciale) qui est en défaut sur la première déclaration n'est pas fourni par l'utilisateur.
-
maintenant un agrégat ne peut pas avoir de attachez-ou-équivalent-initialiseurs pour les membres de données non statiques. Qu'est-ce que cela signifie? Eh bien, c'est juste 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é rend la classe plus un agrégat parce qu'il est fondamentalement équivalent à fournir votre propre constructeur par défaut.
ainsi, ce qui est un agrégat n'a pas beaucoup changé du tout. C'est toujours la même idée de base, adaptés aux nouvelles fonctionnalités.
et les gousses?
les gousses ont subi beaucoup de changements. Beaucoup de règles précédentes sur Les gousses ont été assouplies dans cette nouvelle norme, et la façon dont la définition est fournie dans la norme a été radicalement changé.
l'idée d'un POD est de capturer essentiellement deux propriétés distinctes:
- il supporte l'initialisation statique, et
- compiler un POD en C++ vous donne la même configuration mémoire qu'une structure compilée en C.
pour cette Raison, la définition a été divisé en deux concepts distincts: trivial classes et standard-layout classes, parce que celles-ci sont plus utiles que POD. La norme utilise maintenant rarement le terme POD, préférant les concepts plus spécifiques trivial et standard-layout .
la nouvelle définition dit essentiellement qu'un POD est une classe qui est à la fois trivial et a la norme-layout, et cette propriété doit tenir récursivement pour tous les membres de données non statiques:
une structure de gousse est une classe non syndicale qui est à la fois une classe triviale et une classe de la norme-cadre., et n'a pas de données non-statiques membres de type non-POD struct, non-POD union (ou tableau de tels types). De même, un syndicat de gousse est un syndicat qui est à la fois une classe triviale et une classe de layout standard, et pas de données non statiques membres de type non-POD struct, non-POD union (ou tableau de tels types). Une classe de gousse est un classe qui est soit une structure de gousse ou un syndicat de gousse.
passons en revue chacune de ces deux propriétés en détail séparément.
Trivial classes
Trivial est la première propriété mentionnée ci-dessus: les classes triviales supportent l'initialisation statique.
Si une classe est trivialement copiable (un super ensemble de classes triviales), il est correct de copier sa représentation au-dessus de l'endroit 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 aucun constructeur de copie non trivial (12.8),
- n'a aucun constructeur de déplacement non trivial (12.8),
- n'a pas d'opérateurs non-trivial copy assignment (13.5.3, 12.8),
- n'a pas d'opérateurs non-trivial move assignment (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.
[ Note: en particulier, une classe trivialement copiable ou triviale n'a pas de fonctions virtuelles ou des classes de base virtuelles. - note finale ]
alors, qu'est-ce que toutes ces choses triviales et non triviales?
un constructeur de copie/déplacement pour la classe X est trivial s'il n'est pas fourni par l'utilisateur et si
- la classe X n'a pas de fonctions virtuelles (10.3) et pas de classes de base virtuelles (10.1), et
- le constructeur sélectionné pour copier / déplacer chaque sous-objet de la classe de base directe est trivial, et
- pour chaque élément de données non statique de X qui est du type de classe (ou de son tableau), le constructeur sélectionné pour copier/déplacer le membre est triviale;
sinon le constructeur copy/move n'est pas trivial.
fondamentalement cela signifie qu'une copie ou un constructeur de déplacement est trivial si elle n'est pas fournie par l'utilisateur, la classe n'a rien de virtuel en elle, et cette propriété est retenue récursivement pour tous les membres de la classe et pour la classe de base.
la définition d'un opérateur d'affectation trivial copy/move est très similaire, remplaçant simplement le mot" constructeur "par"opérateur d'affectation".
trivial destructeur a aussi une définition similaire, avec la contrainte 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 données non statiques membres de corset ou égal initialiseurs , dont nous avons vu ci-dessus.
voici quelques exemples pour éclaircir tout:
// 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
};
norme-cadre
norme-cadre est la deuxième propriété. La norme mentionne que celles-ci sont utiles pour communiquer avec d'autres langues, et c'est parce qu'une classe de mise en page standard a la même mise en page mémoire que l'équivalent (2) structure ou union.
il s'agit d'une autre propriété qui doit être détenue 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 assouplie est que les classes de la norme-cadre doivent avoir tous les éléments de données non statiques avec le même contrôle d'accès. Auparavant ceux-ci devaient être tous public , mais maintenant vous pouvez les faire privés ou protégés, aussi longtemps qu'ils sont tous "1519160920 privées" ou tous protégées.
lors de l'utilisation de l'héritage, une seule classe dans toute l'arborescence 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 briser les règles d'aliasing), sinon, ce n'est pas une classe standard-layout.
C'est ainsi que la définition va dans le texte standard:
une classe de la norme-cadre est une classe qui:
- ne comporte aucun élément de données non statiques de type non-standard-layout class (ou tableau de tels types) ou référence,
- n'a pas de fonctions virtuelles (10.3) et pas de classes de base virtuelles (10.1),
- a le même contrôle d'accès (Clause 11) pour tous les membres de données non statiques,
- n'a pas de classes de base non normalisées,
- soit n'a aucun élément de données non statiques dans la classe la plus dérivée et au plus une classe de base avec non-membres de données statiques, ou n'a pas de classes de base avec les données membres statiques, et
-n'a pas de classes de base du même type que le premier membre de données non statique.
une structure de la norme-cadre est une classe de la norme-cadre définie avec la structure de classe-clé ou le classe-classe de clé.
un syndicat-cadre est une classe de la norme-cadre définie avec le syndicat-clé.
[ Note: les classes de la norme-cadre sont utiles pour communiquer avec du code écrit dans d'autres langages de programmation. Leur disposition est spécifiée au point 9.2. - fin de la note ]
et voyons quelques exemples.
// empty classes have standard-layout
struct StandardLayout1 {};
struct StandardLayout2 {
int x;
};
struct StandardLayout3 {
private: // both are private, so it's ok
int x;
int y;
};
struct StandardLayout4 : StandardLayout1 {
int x;
int y;
void f(); // perfectly fine to have non-virtual functions
};
struct StandardLayout5 : StandardLayout1 {
int x;
StandardLayout1 y; // can have members of base type if they're not the first
};
struct StandardLayout6 : StandardLayout1, StandardLayout5 {
// can use multiple inheritance as long only
// one class in the hierarchy has non-static data members
};
struct StandardLayout7 {
int x;
int y;
StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};
struct StandardLayout8 {
public:
StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
int x;
};
struct StandardLayout9 {
int x;
static NonStandardLayout1 y; // no restrictions on static members
};
struct NonStandardLayout1 {
virtual f(); // cannot have virtual functions
};
struct NonStandardLayout2 {
NonStandardLayout1 X; // has non-standard-layout member
};
struct NonStandardLayout3 : StandardLayout1 {
StandardLayout1 x; // first member cannot be of the same type as base
};
struct NonStandardLayout4 : StandardLayout3 {
int z; // more than one class has non-static data members
};
struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class
Conclusion
avec ces nouvelles règles beaucoup plus de types peuvent être des gousses maintenant. Et même si un type n'est pas POD, nous pouvons profiter de certaines des propriétés POD séparément (si ce n'est qu'un de trivial ou standard-layout).
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;
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
cela est couvert dans la section 8.5.1
agrégats qui nous donne la définition suivante:
un agrégat est un tableau ou une classe (Clause 9) qui n'est pas fourni par l'utilisateur. constructeurs (12.1), Non privés ou protégés éléments de données non statiques (Clause 11), Pas de classes de base (Clause 10), et pas de fonctions virtuelles (10.3).
la seule modification consiste à ajouter initialisateurs de membres de la catégorie ne rend pas une catégorie non agrégée. Ainsi, l'exemple suivant de C++11 initialisation agrégée pour les classes avec initialisateurs membres in-pace :
struct A
{
int a = 3;
int b = 3;
};
n'était pas un agrégat en C++11 mais en C++14. Ce changement est couvert dans N3605: membres initialisateurs et agrégats , qui a le résumé suivant:
Bjarne Stroustrup et Richard Smith ont soulevé la question de l'agrégat initialisation et membres-initialisateurs ne travaillant pas ensemble. Ce paper propose de régler la question en adoptant le libellé proposé par Smith qui supprime une restriction que les agrégats ne peuvent pas avoir membres-initialisateurs.
POD reste le même
la définition de Pod ( plain old data ) struct est couverte dans la section 9
Classes qui dit:
UN POD struct 110 est un non-union de la classe qui est à la fois simple et classe une classe de norme-cadre, et n'a pas de membres de données non statiques de type non-POD struct, non-POD de l'union (ou d'une matrice de ce type). Pareillement, un POD union est un syndicat qui est à la fois une classe triviale et classe de la norme-cadre, et ne comporte aucun élément de données non statique de type non-POD struct, non-POD de l'union (ou d'une matrice de ce type). Une classe de gousse est une classe qui est soit une structure POD ou un syndicat POD.
qui est la même formulation que C++11.
pouvez-vous s'il vous plaît élaborer les règles suivantes:
je vais essayer:
a) les classes de la norme-cadre doivent comporter tous les éléments de données non statiques ayant 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 du public
et du private
.
le raisonnement qui les sous-tend va dans le sens d'une distinction entre la" norme-cadre "et la" non-norme-cadre". À savoir, donner au compilateur la liberté de choisir comment mettre les choses dans la mémoire. Ce n'est pas seulement à propos de vtable des pointeurs.
quand ils ont standardisé C++ en 98, ils ont dû prédire comment les gens l'implémenteraient. Bien qu'ils aient eu un peu d'expérience de mise en œuvre avec divers parfums de C++, ils n'étaient pas certaines 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. Il a donné aux compilateurs C++ une grande latitude sur la disposition des membres pour la plupart des classes. Fondamentalement, les types de gousses étaient destinés à être des cas spéciaux, quelque chose que vous avez spécifiquement écrit pour une raison.
Quand C++11 était travaillé, ils avaient beaucoup plus d'expérience avec les compilateurs. Et ils se sont rendu compte que... Les auteurs de compilateurs C++ sont vraiment paresseux. Ils avaient toute cette liberté, mais ils n'ont pas faire rien avec elle.
les règles de la norme-cadre codifient plus ou moins la pratique courante: la plupart des compilateurs n'ont pas vraiment eu besoin de changer grand chose pour les mettre en œuvre (en dehors peut-être de quelques trucs pour les traits de type correspondants).
Maintenant, quand il est venu à public
/ private
, les choses sont différent. La liberté de réordonner les membres qui sont public
vs. private
peut avoir de l'importance pour le compilateur, en particulier dans les constructions de débogage. Et puisque le point de la norme layout est qu'il y a compatibilité avec d'autres langues, vous ne pouvez pas faire en sorte que la mise en page soit différente dans debug vs. release.
il y a le fait que cela ne blesse pas vraiment l'utilisateur. Si vous faites une classe encapsulée, les chances sont bonnes que tous les membres de vos données seront private
en tout cas. Vous n'exposez généralement pas les membres de données publiques sur les types entièrement encapsulés. Donc, ce ne serait qu'un problème pour les quelques utilisateurs qui veulent faire cela, qui veulent Cette division.
donc ce n'est pas une grosse perte.
b) une seule classe dans l'ensemble de l'arbre de l'héritage peut avoir des membres de données non statiques,
la raison d'être de celui-ci revient à la raison pour laquelle ils ont normalisé la norme-cadre nouveau: la pratique courante.
il y a Non pratique courante quand il s'agit d'avoir deux membres d'un arbre héréditaire qui stockent réellement des choses. Certains mettent la classe de base avant la dérivée, d'autres la font de l'autre côté. De quelle façon commandez-vous les membres s'ils viennent de deux classes de base? Et ainsi de suite. Les compilateurs divergent considérablement sur ces questions.
aussi, grâce à la règle zéro/un/infini, une fois que vous dites que vous pouvez avoir deux cours avec des membres, vous pouvez en dire autant que vous voulez. Cela nécessite d'ajouter beaucoup de règles de mise en page pour gérer cela. Vous devez dire comment fonctionne l'héritage multiple, quelles classes mettent leurs données avant d'autres classes, etc. C'est beaucoup de règles, pour très peu de gain matériel.
vous ne pouvez pas faire tout ce qui n'a pas de fonctions virtuelles et une mise en page standard par défaut du constructeur.
et le premier membre de données non statiques ne peut pas être d'un type de classe de base (cela pourrait briser les règles d'alias).
Je ne peux pas vraiment lui parler. Je ne suis pas assez instruit dans les règles d'aliasing 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-à-dire:
struct Base {};
struct Derived : Base { Base b; };
Derived d;
static_cast<Base*>(&d) == &d.b;
et c'est probablement contre les règles d'alias de C++. D'une certaine façon.
cependant, considérer ceci: comment utile avoir la capacité de faire cela jamais réellement être? Comme une seule classe peut avoir des membres de données non statiques, alors Derived
doit être cette classe (puisqu'elle a un Base
comme membre). Ainsi Base
doit être vide (de données). Et si Base
est vide, ainsi que une classe de base... pourquoi un membre de données de tout?
puisque Base
est vide, il n'a pas d'état. Ainsi, toute fonction membre non statique fera ce qu'elle fait en fonction de ses paramètres, et non de son pointeur this
.
Donc encore une fois: pas de grosse perte.
variations en C++17
Téléchargez la version finale de la norme internationale c++17 ici .
agrégats
C++17 élargit et améliore les agrégats et l'initialisation agrégée. La bibliothèque standard comprend également une classe de caractères std::is_aggregate
. Voici la définition officielle des sections 11.6.1.1 et 11.6.1.2 (références internes). élidée):
un agrégat est un réseau ou une classe avec
- non fourni par l'utilisateur, explicite ou héritées des constructeurs,
- aucun membre privé ou protégé de données non statiques,
- pas de fonctions virtuelles, et
- pas de cours de base virtuels, privés ou protégés.
[ Note: L'initialisation agrégée ne permet pas l'accès membres ou constructeurs de la classe de base protégée et privée. -fin de la note]
Les éléments d'un agrégat sont:
- pour un tableau, les éléments du tableau dans l'ordre croissant de l'indice, ou
- pour une classe, les classes de base directes dans l'ordre de déclaration, suivies par les données directes non statiques membres qui ne sont pas membres d'un syndicat anonyme, dans l'ordre de déclaration.
qu'est-ce qui a changé?
- les agrégats peuvent maintenant avoir des classes de base publiques et non virtuelles. En outre, il n'est pas nécessaire que les classes de base soient des agrégats. Si elles ne sont pas des agrégats, elles sont initialisées 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::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
- Explicite par défaut des constructeurs sont interdits
struct D // not an aggregate
{
int i = 0;
D() = default;
explicit D(D const&) = default;
};
- les constructeurs héritiers sont interdits
struct B1
{
int i1;
B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
using B1::B1;
};
Classes Triviales
la définition de classe 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 à 12.0.6 (références internes supprimées):
une classe trivialement copiable est une classe:
- où chaque copie constructeur, déplacer constructeur, opérateur de copie d'affectation, et opérateur de déménagement d'affectation est soit supprimé ou trivial,
- qui comporte au moins un constructeur de copies non supprimé, un constructeur de mouvements, un opérateur d'assignation de copies ou un opérateur d'assignation de mouvements, et
1519230920" -qui a un destructeur trivial, non-supprimé.
Un trivial classe est une classe qui est trivialement copiable et a un ou plusieurs constructeurs par défaut, qui sont soit trivial ou supprimés, et au moins un qui n'est pas supprimé. [ Note: En particulier, un trivialement copiable ou trivial class n'a pas de fonctions virtuelles ou de classes de base virtuelles.- fin de la note ]
Changements:
- sous C++14, pour qu'une classe soit triviale, la classe ne pouvait pas avoir de Copy/move constructor/assignment operators qui étaient non trivial. Cependant, alors un implicitement a déclaré comme constructeur/opérateur en défaut pourrait être non-trivial et pourtant défini comme supprimé parce que, par exemple, la classe contenait un sous-objet de type de 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 toute la classe serait non-triviale. Un problème similaire existait avec les destructeurs. C++17 clarifie que la présence d'un tel constructeur/opérateur n'entraîne pas une copie non triviale de la classe, donc non triviale, et qu'un la classe trivially copiable doit avoir un destructeur trivial, non-supprimé. DR1734 , DR1928
- C++14 a permis à une classe trivialement copiable, donc une classe triviale, de faire déclarer chaque copie/déplacement de constructeur/opérateur d'affectation comme supprimé. Si une classe est aussi la norme-cadre, elle peut toutefois être copiée/déplacée légalement avec
std::memcpy
. C'était une contradiction sémantique, parce que, en définissant comme supprimé tout le concepteur de la classe avait clairement l'intention que la classe ne puisse pas être copiée/déplacée, mais la classe répondait quand même à la définition d'une classe trivialement copiable. Par conséquent, dans C++17, nous avons une nouvelle clause stipulant que la classe trivialement copiable doit avoir au moins une classe triviale, non-supprimée (mais pas nécessairement accessible au public) copie/déplacement constructeur/opérateur d'affectation. Voir N4148 , DR1734 1519440920"" - La troisième modification technique concerne un problème similaire avec les constructeurs par défaut. Sous C++14, une classe peut avoir des constructeurs par défaut trivial qui ont été implicitement définis comme supprimés, tout en restant une classe trivial. La nouvelle définition clarifie qu'une classe trivial doit avoir au moins un constructeur par défaut trivial, non supprimé. Voir DR1496 1519440920"
Classes-cadres
le la définition de la norme-cadre a également été remaniée pour tenir compte des rapports de défauts. Encore une fois, les changements étaient de nature technique. Voici le texte de la norme (12.0.7). Comme auparavant, les références internes sont supprimées:
d'Une classe S est un standard-classe de mise en page s'il:
- ne comporte pas de données non statiques des éléments de type non standard-layout class (ou tableau de tels types) ou de référence,
- n'a pas de fonctions virtuelles et aucun virtuel les classes de base,
- 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 non normalisées,
- a au plus un sous-objet de classe de base de n'importe quel type donné,
- tous les membres de données non statiques et les champs de bits de la classe et de ses classes de base ont été déclarés pour la première fois dans la même classe, et
- ne comporte aucun élément de l'ensemble M(s) de types (définis ci-dessous) en tant que classe de base.108
M (X) est défini comme suit:
- Si X est un type de classe non-union avec aucun(peut-être hérité) membres de données non-statiques, L'ensemble M (X) est vide.
- Si X est un type de classe non-union dont le premier membre de données non-statiques a le type X0(où ledit Membre peut être un syndicat anonyme), L'ensemble M(X) se compose de X0 et les éléments de M (X0).
- Si X est un type de syndicat, 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 statiques 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 non-classe, non-type de tableau, l'ensemble M(X) est vide.
[ Note: M (X) est l'ensemble des types de tous les sous-projets hors classe de base qui sont garantis dans un la classe-cadre doit être à zéro offset dans la note X.]
[Exemple:
-fin de l'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 attribués à la même adresse.
Changements:
- clarifie que le 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, et non à des classes où ils peuvent être hérités, et étend cette exigence aux champs de bits non statiques. Il a également précisé qu'une classe de la norme-cadre "a au plus un sous-objet de classe de base de n'importe quel type donné."Voir DR1813 , DR1881
- la définition de la norme-cadre n'a jamais été permet au type de n'importe quelle classe de base d'être le même type que le premier membre de données non statique. Il est d'éviter une situation où un membre de données à l'offset 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 plus récursive de "l'ensemble des types de tous les sous-objets hors classe de base qui sont garantis dans une classe de la norme-cadre d'être à un décalage zéro" de manière à interdire de tels types d'être le type de n'importe quelle classe de base. Voir DR1672 , DR2120 .