Object
Les programmes C++ créent, détruisent, référencent, accèdent et manipulent des objets .
Un objet, en C++, a
-
taille (peut être déterminée avec
sizeof); -
exigence d'alignement (peut être déterminée avec
alignof); - durée de stockage (automatique, statique, dynamique, thread-local);
- durée de vie (délimitée par la durée de stockage ou temporaire);
- type ;
- valeur (qui peut être indéterminée, par ex. pour les types non-classe initialisés par défaut );
- optionnellement, un nom .
Les entités suivantes ne sont pas des objets : valeur, référence, fonction, énumérateur, type, membre non statique de classe, template, spécialisation de template de classe ou de fonction, espace de noms, pack de paramètres, et this .
Une variable est un objet ou une référence qui n'est pas un membre de données non statique, qui est introduit par une déclaration .
Sommaire
Création d'objetsLes objets peuvent être explicitement créés par des définitions , new expressions , throw expressions , en modifiant le membre actif d'une union et en évaluant des expressions qui nécessitent des objets temporaires . L'objet créé est unique dans la création explicite d'objet. Les objets de types à durée de vie implicite peuvent également être créés implicitement par
Zéro ou plusieurs objets peuvent être créés dans la même région de mémoire, tant que cela donne un comportement défini au programme. Si une telle création est impossible, par exemple en raison d'opérations conflictuelles, le comportement du programme est indéfini. Si plusieurs ensembles de tels objets implicitement créés donneraient un comportement défini au programme, il n'est pas spécifié quel ensemble d'objets est créé. En d'autres termes, les objets implicitement créés ne sont pas requis d'être définis de manière unique. Après avoir implicitement créé des objets dans une région de stockage spécifiée, certaines opérations produisent un pointeur vers un objet créé approprié . L'objet créé approprié possède la même adresse que la région de stockage. De même, le comportement n'est indéfini que si aucune valeur de pointeur de ce type ne peut donner un comportement défini au programme, et il n'est pas spécifié quelle valeur de pointeur est produite s'il existe plusieurs valeurs donnant un comportement défini au programme. #include <cstdlib> struct X { int a, b; }; X* MakeX() { // Un des comportements définis possibles : // l'appel à std::malloc crée implicitement un objet de type X // et ses sous-objets a et b, et retourne un pointeur vers cet objet X X* p = static_cast<X*>(std::malloc(sizeof(X))); p->a = 1; p->b = 2; return p; } Appel à std::allocator::allocate ou aux fonctions membres spéciales de copie/déplacement implicitement définies des types union peut également créer des objets. Représentation d'objet et représentation de valeurCertains types et objets ont des représentations d'objet et des représentations de valeur , elles sont définies dans le tableau ci-dessous :
Les bits dans la représentation objet d'un type ou d'un objet qui ne font pas partie de la représentation valeur sont des bits de remplissage . Pour les types TriviallyCopyable , la représentation de la valeur fait partie de la représentation de l'objet, ce qui signifie que la copie des octets occupés par l'objet en mémoire est suffisante pour produire un autre objet avec la même valeur (sauf si l'objet est un sous-objet potentiellement chevauchant, ou si la valeur est une représentation piégée de son type et que son chargement dans le CPU déclenche une exception matérielle, telle que les valeurs flottantes SNaN ("signalling not-a-number") ou les entiers NaT ("not-a-thing")). Bien que la plupart des implémentations n'autorisent pas les représentations piégées, les bits de remplissage, ou les représentations multiples pour les types entiers, il existe des exceptions ; par exemple, une valeur d'un type entier sur Itanium peut être une représentation piégée . L'inverse n'est pas nécessairement vrai : deux objets d'un type TriviallyCopyable avec des représentations objet différentes peuvent représenter la même valeur. Par exemple, plusieurs motifs binaires de nombres à virgule flottante représentent la même valeur spéciale NaN . Plus communément, des bits de remplissage peuvent être introduits pour satisfaire les exigences d'alignement , les tailles des champs de bits , etc. #include <cassert> struct S { char c; // valeur de 1 octet // 3 octets de bits de remplissage (en supposant alignof(float) == 4) float f; // valeur de 4 octets (en supposant sizeof(float) == 4) bool operator==(const S& arg) const // égalité basée sur la valeur { return c == arg.c && f == arg.f; } }; void f() { assert(sizeof(S) == 8); S s1 = {'a', 3.14}; S s2 = s1; reinterpret_cast<unsigned char*>(&s1)[2] = 'b'; // modifier certains bits de remplissage assert(s1 == s2); // la valeur n'a pas changé } Pour les objets de type char , signed char , et unsigned char (sauf s'ils sont des champs de bits surdimensionnés), chaque bit de la représentation de l'objet doit participer à la représentation de la valeur et chaque motif de bits possible représente une valeur distincte (aucun bit de remplissage, bit piégé ou représentation multiple autorisé). Sous-objetsUn objet peut avoir des sous-objets . Ceux-ci incluent
Un objet qui n'est pas un sous-objet d'un autre objet est appelé objet complet . Si un objet complet, un sous-objet membre ou un élément de tableau est de type classe , son type est considéré comme la classe la plus dérivée , pour le distinguer du type classe de tout sous-objet de classe de base. Un objet de type classe la plus dérivée ou de type non-classe est appelé objet le plus dérivé . Pour une classe,
sont appelés ses sous-objets potentiellement construits . Taille
Un sous-objet est un
sous-objet potentiellement chevauchant
s'il s'agit d'un sous-objet de classe de base
ou d'un membre de données non statique déclaré avec l'attribut
Un objet obj ne peut possiblement avoir une taille nulle que si toutes les conditions suivantes sont satisfaites :
Pour un objet obj satisfaisant toutes les conditions ci-dessus :
Voir l'optimisation des classes de base vides pour plus de détails. Tout objet non-champ de bits de taille non nulle doit occuper un ou plusieurs octets de stockage, incluant chaque octet occupé (en totalité ou en partie) par l'un de ses sous-objets. Le stockage occupé doit être contigu si l'objet est de type trivialement copiable ou de disposition standard (depuis C++11) . AdresseSauf si un objet est un champ de bits ou un sous-objet de taille nulle, l' adresse de cet objet est l'adresse du premier byte qu'il occupe. Un objet peut contenir d'autres objets, auquel cas les objets contenus sont imbriqués dans l'objet précédent. Un objet a est imbriqué dans un autre objet b si l'une des conditions suivantes est satisfaite :
Un objet est un objet potentiellement non unique s'il s'agit de l'un des objets suivants :
Pour deux objets non-champs de bits ayant des durées de vie qui se chevauchent :
// les littéraux de caractères sont toujours uniques static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // toujours vrai // le caractère 'x' accessible depuis « r », « s » et « il » // peut avoir la même adresse (c.-à-d. ces objets peuvent partager le stockage) static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = {'x'}; const bool b2 = r != il.begin(); // résultat non spécifié const bool b3 = r != s; // résultat non spécifié const bool b4 = il.begin() != &test1; // toujours vrai const bool b5 = r != &test1; // toujours vrai Objets polymorphes
Les objets d'un type de classe qui déclare ou hérite d'au moins une fonction virtuelle sont des objets polymorphes. Dans chaque objet polymorphe, l'implémentation stocke des informations supplémentaires (dans toutes les implémentations existantes, il s'agit d'un pointeur sauf s'il est optimisé), qui sont utilisées par les appels de
fonction virtuelle
et par les fonctionnalités RTTI (
Pour les objets non-polymorphiques, l'interprétation de la valeur est déterminée à partir de l'expression dans laquelle l'objet est utilisé, et est décidée au moment de la compilation.
Exécuter ce code
#include <iostream> #include <typeinfo> struct Base1 { // type polymorphe : déclare un membre virtuel virtual ~Base1() {} }; struct Derived1 : Base1 { // type polymorphe : hérite d'un membre virtuel }; struct Base2 { // type non polymorphe }; struct Derived2 : Base2 { // type non polymorphe }; int main() { Derived1 obj1; // objet1 créé avec le type Derived1 Derived2 obj2; // objet2 créé avec le type Derived2 Base1& b1 = obj1; // b1 fait référence à l'objet obj1 Base2& b2 = obj2; // b2 fait référence à l'objet obj2 std::cout << "Expression type of b1: " << typeid(decltype(b1)).name() << '\n' << "Expression type of b2: " << typeid(decltype(b2)).name() << '\n' << "Object type of b1: " << typeid(b1).name() << '\n' << "Object type of b2: " << typeid(b2).name() << '\n' << "Size of b1: " << sizeof b1 << '\n' << "Size of b2: " << sizeof b2 << '\n'; } Sortie possible : Expression type of b1: Base1 Expression type of b2: Base2 Object type of b1: Derived1 Object type of b2: Base2 Size of b1: 8 Size of b2: 1 Aliasing strict
Accéder à un objet en utilisant une expression d'un type différent de celui avec lequel il a été créé est un comportement indéfini dans de nombreux cas, consultez
AlignementChaque type d'objet possède la propriété appelée exigence d'alignement, qui est une valeur entière non négative (de type std::size_t, et toujours une puissance de deux) représentant le nombre d'octets entre des adresses successives auxquelles des objets de ce type peuvent être alloués.
Chaque type d'objet impose son exigence d'alignement à chaque objet de ce type; un alignement plus strict (avec une exigence d'alignement plus grande) peut être demandé en utilisant Afin de satisfaire aux exigences d'alignement de tous les membres non statiques d'une classe, des bits de remplissage peuvent être insérés après certains de ses membres. Exécuter ce code #include <iostream>
// objects of type S can be allocated at any address
// because both S.a and S.b can be allocated at any address
struct S
{
char a; // size: 1, alignment: 1
char b; // size: 1, alignment: 1
}; // size: 2, alignment: 1
// objects of type X must be allocated at 4-byte boundaries
// because X.n must be allocated at 4-byte boundaries
// because int's alignment requirement is (usually) 4
struct X
{
int n; // size: 4, alignment: 4
char c; // size: 1, alignment: 1
// three bytes of padding bits
}; // size: 8, alignment: 4
int main()
{
std::cout << "alignof(S) = " << alignof(S) << '\n'
<< "sizeof(S) = " << sizeof(S) << '\n'
<< "alignof(X) = " << alignof(X) << '\n'
<< "sizeof(X) = " << sizeof(X) << '\n';
}
Sortie possible : alignof(S) = 1
sizeof(S) = 2
alignof(X) = 4
sizeof(X) = 8
L'alignement le plus faible (la plus petite exigence d'alignement) est l'alignement de Si une demande pour un alignement étendu spécifique dans un contexte spécifique n'est pas supportée par une implémentation, le programme est mal formé.
NotesLes objets en C++ ont une signification différente des objets dans la programmation orientée objet (POO) :
Dans le rapport de défaut
P0593R6
, la création implicite d'objet était considérée comme se produisant lors de la création d'un tableau d'octets ou de l'invocation d'une
fonction d'allocation
(qui est potentiellement définie par l'utilisateur et
Rapports de défautsLes rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
Voir aussi
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||