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 .
Table des matières |
Création d'objets
Les 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
- sauf pendant l'évaluation constante, les opérations qui commencent la durée de vie d'un tableau de type unsigned char ou std::byte (depuis C++17) , auquel cas de tels objets sont créés dans le tableau,
- appel aux fonctions d'allocation suivantes, auquel cas de tels objets sont créés dans le stockage alloué :
-
- operator new (sauf pendant l'évaluation constante)
- operator new[] (sauf pendant l'évaluation constante)
- std::malloc
- std::calloc
- std::realloc
| (depuis C++17) |
- appel aux fonctions de copie de la représentation d'objet suivantes, auquel cas de tels objets sont créés dans la région de stockage de destination ou le résultat :
| (depuis C++20) |
|
(depuis C++23) |
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 valeur
Certains types et objets ont des représentations d'objet et des représentations de valeur , elles sont définies dans le tableau ci-dessous :
| Entité | Représentation objet | Représentation valeur |
|---|---|---|
un type objet complet
T
|
la séquence de
N
unsigned
char
occupée par un objet complet de type
T
non-
champ de bits
, où
N
vaut
sizeof
(
T
)
|
l'ensemble des bits dans la représentation objet de
T
qui participent à la représentation d'une valeur de type
T
|
un objet complet non-champ de bits
obj
de type
T
|
les octets de
obj
correspondant à la représentation objet de
T
|
les bits de
obj
correspondant à la représentation valeur de
T
|
| un objet champ de bits bf | la séquence de N bits occupée par bf , où N est la largeur du champ de bits | l'ensemble des bits dans la représentation objet de bf qui participent à la représentation de la valeur de bf |
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-objets
Un objet peut avoir des sous-objets . Ceux-ci incluent
- objets membres
- sous-objets de classe de base
- éléments de tableau
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,
- ses membres de données non statiques,
- ses classes de base directes non virtuelles, et,
- si la classe n'est pas abstraite , ses classes de base virtuelles
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
[[
no_unique_address
]]
(depuis C++20)
.
Un objet obj ne peut possiblement avoir une taille nulle que si toutes les conditions suivantes sont satisfaites :
- obj est un sous-objet potentiellement chevauchant.
- obj est d'un type classe sans fonctions membres virtuelles et classes de base virtuelles.
- obj n'a aucun sous-objet de taille non nulle ou champs de bits sans nom de longueur non nulle.
Pour un objet obj satisfaisant toutes les conditions ci-dessus :
- Si obj est un sous-objet de classe de base d'un type de classe standard-layout (depuis C++11) sans membres de données non statiques, il a une taille nulle.
- Sinon, il est défini par l'implémentation dans quelles circonstances obj a une taille nulle.
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) .
Adresse
Sauf 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 :
- a est un sous-objet de b .
- b fournit le stockage pour a .
- Il existe un objet c où a est imbriqué dans c , et c est imbriqué dans b .
Un objet est un objet potentiellement non unique s'il s'agit de l'un des objets suivants :
- Un objet string literal .
|
(depuis C++11) |
- Un sous-objet d'un objet potentiellement non unique.
Pour deux objets non-champs de bits ayant des durées de vie qui se chevauchent :
- Si l'une des conditions suivantes est satisfaite, elles peuvent avoir la même adresse :
-
- L'un d'eux est imbriqué dans l'autre.
- L'un d'eux est un sous-objet de taille zéro, et leurs types ne sont pas similaires .
- Ils sont tous deux des objets potentiellement non uniques.
- Sinon, ils ont toujours des adresses distinctes et occupent des octets de stockage disjoints.
// 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 (
dynamic_cast
et
typeid
) pour déterminer, à l'exécution, le type avec lequel l'objet a été créé, indépendamment de l'expression dans laquelle il est utilisé.
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.
#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
reinterpret_cast
pour la liste des exceptions et des exemples.
Alignement
Chaque type d'objet possède une 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 les adresses successives auxquelles les objets de ce type peuvent être alloués.
|
L'exigence d'alignement d'un type peut être interrogée avec
|
(depuis C++11) |
Chaque type d'objet impose son exigence d'alignement sur chaque objet de ce type
; un alignement plus strict (avec une exigence d'alignement plus grande) peut être demandé en utilisant
alignas
(depuis C++11)
. Tenter de créer un objet dans un espace mémoire qui ne respecte pas les exigences d'alignement du type de l'objet est un comportement indéfini.
Afin de satisfaire les 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.
#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 char , signed char , et unsigned char , qui est égal à 1 ; le plus grand alignement fondamental de tout type est défini par l'implémentation et égal à l'alignement de std::max_align_t (depuis C++11) .
Les alignements fondamentaux sont pris en charge pour les objets de toutes les durées de stockage.
|
Si l'alignement d'un type est rendu plus strict (plus grand) que
std::max_align_t
en utilisant
Allocator doivent gérer correctement les types sur-alignés. |
(depuis C++11) |
|
Il est défini par l'implémentation si new expressions et (jusqu'en C++17) std::get_temporary_buffer prennent en charge les types sur-alignés. |
(depuis C++11)
(jusqu'en C++20) |
Notes
Les objets en C++ ont une signification différente des objets dans la programmation orientée objet (POO) :
| Objets en C++ | Objets en POO |
|---|---|
|
peuvent avoir n'importe quel type d'objet
(voir std::is_object ) |
doivent avoir un type classe |
| pas de concept d'« instance » |
possèdent le concept d'« instance » (et il existe des mécanismes comme
instanceof
pour détecter la relation « instance-de »)
|
| pas de concept d'« interface » |
possèdent le concept d'« interface » (et il existe des mécanismes comme
instanceof
pour détecter si une interface est implémentée)
|
| le polymorphisme doit être explicitement activé via des membres virtuels | le polymorphisme est toujours activé |
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
constexpr
) pendant l'évaluation constante. Cependant, cette autorisation a causé un indéterminisme dans l'évaluation constante qui était indésirable et impossible à implémenter sous certains aspects. Par conséquent,
P2747R2
a interdit une telle création implicite d'objet dans l'évaluation constante. Nous traitons intentionnellement ce changement comme un rapport de défaut bien que l'ensemble du document ne le soit pas.
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 633 | C++98 | les variables ne pouvaient être que des objets | elles peuvent aussi être des références |
| CWG 734 | C++98 |
il n'était pas spécifié si les variables définies
dans la même portée qui sont garanties d'avoir la même valeur peuvent avoir la même adresse |
l'adresse est garantie d'être
différente si leurs durées de vie se chevauchent, indépendamment de leurs valeurs |
| CWG 1189 | C++98 |
deux sous-objets de classe de base du même
type pouvaient avoir la même adresse |
ils ont toujours
des adresses distinctes |
| CWG 1861 | C++98 |
pour les champs de bits surdimensionnés de types caractères
étroits, tous les bits de la représentation d'objet participaient encore à la représentation de valeur |
permet des bits de remplissage |
| CWG 2489 | C++98 |
char
[
]
ne peut pas fournir de stockage, mais les objets
pouvaient être implicitement créés dans son stockage |
les objets ne peuvent pas être implicitement créés
dans le stockage de char [ ] |
| CWG 2519 | C++98 | la définition de la représentation d'objet ne traitait pas des champs de bits | traite des champs de bits |
| CWG 2719 | C++98 |
le comportement de création d'un objet
dans un stockage non aligné n'était pas clair |
le comportement est
indéfini dans ce cas |
| CWG 2753 | C++11 |
il n'était pas clair si un tableau de support d'une
liste d'initialisation peut partager le stockage avec un littéral de chaîne |
ils peuvent partager le stockage |
| CWG 2795 | C++98 |
lors de la détermination si deux objets avec des durées de vie
se chevauchant peuvent avoir la même adresse, si l'un d'eux est un sous-objet de taille zéro, ils pouvaient avoir des types distincts similaires |
permet seulement des types non similaires |
| P0593R6 | C++98 |
le modèle d'objet précédent ne supportait pas de nombreux
idiomes utiles requis par la bibliothèque standard et n'était pas compatible avec les types effectifs en C |
création d'objet implicite ajoutée |
Voir aussi
|
Documentation C
pour
Object
|