Definitions and ODR (One Definition Rule)
Définitions sont des déclarations qui définissent entièrement l'entité introduite par la déclaration. Toute déclaration est une définition, sauf pour les suivantes :
- Une déclaration de fonction sans corps de fonction :
int f(int); // déclare, mais ne définit pas f
- Toute déclaration avec un extern spécificateur de classe de stockage ou avec un spécificateur de liaison de langage (tel que extern "C" ) sans initialiseur :
extern const int a; // déclare, mais ne définit pas a extern const int b = 1; // définit b
- Déclaration d'un non-inline (depuis C++17) static data member dans une définition de classe :
struct S { int n; // définit S::n static int i; // déclare, mais ne définit pas S::i inline static int x; // définit S::x }; // définit S int S::i; // définit S::i
struct S { static constexpr int x = 42; // implicitement inline, définit S::x }; constexpr int S::x; // déclare S::x, pas une redéfinition |
(depuis C++17) |
- Déclaration d'un nom de classe (par déclaration anticipée ou par l'utilisation du spécificateur de type élaboré dans une autre déclaration) :
struct S; // déclare, mais ne définit pas S class Y f(class T p); // déclare, mais ne définit pas Y et T (et également f et p)
enum Color : int; // declares, but does not define Color |
(depuis C++11) |
- Déclaration d'un paramètre de template :
template<typename T> // déclare, mais ne définit pas T
- Une déclaration de paramètre dans une déclaration de fonction qui n'est pas une définition :
int f(int x); // déclare, mais ne définit pas f et x int f(int x) // définit f et x { return x + a; }
- Une typedef déclaration :
typedef S S2; // déclare, mais ne définit pas S2 (S peut être incomplet)
using S2 = S; // declares, but does not define S2 (S may be incomplete) |
(depuis C++11) |
- Une using-declaration :
using N::d; // déclare, mais ne définit pas d
|
(depuis C++17) |
|
(depuis C++11) |
- Une déclaration vide (ne définit aucune entité)
- Une directive using (ne définit aucune entité)
extern template f<int, char>; // declares, but does not define f<int, char> |
(depuis C++11) |
- Une spécialisation explicite dont la déclaration n'est pas une définition :
template<> struct A<int>; // déclare, mais ne définit pas A<int>
Une déclaration asm ne définit aucune entité, mais elle est classée comme une définition.
Si nécessaire, le compilateur peut définir implicitement le constructeur par défaut , constructeur de copie , constructeur de déplacement , opérateur d'affectation par copie , opérateur d'affectation par déplacement , et le destructeur .
Si la définition d'un objet résulte en un objet de type incomplet ou de classe abstraite , le programme est mal formé.
Table des matières |
Règle de Définition Unique
Une seule définition de toute variable, fonction, type de classe, type d'énumération , concept (depuis C++20) ou template est autorisée dans toute unité de traduction (certains de ces éléments peuvent avoir plusieurs déclarations, mais une seule définition est autorisée).
Une et une seule définition de chaque fonction ou variable non- inline qui est odr-used (voir ci-dessous) doit apparaître dans l'ensemble du programme (y compris toute bibliothèque standard et définie par l'utilisateur). Le compilateur n'est pas tenu de diagnostiquer cette violation, mais le comportement du programme qui la viole est indéfini.
Pour une fonction inline ou variable inline (depuis C++17) , une définition est requise dans chaque unité de traduction où elle est odr-used .
Pour une classe, une définition est requise partout où la classe est utilisée d'une manière qui nécessite qu'elle soit complète .
Il peut y avoir plus d'une définition dans un programme pour chacun des éléments suivants : type classe, type énumération, fonction inline , variable inline (depuis C++17) , entité template (template ou membre de template, mais pas une spécialisation complète de template ), à condition que toutes les conditions suivantes soient satisfaites :
- Chaque définition apparaît dans une unité de traduction différente.
|
(depuis C++20) |
- Chaque définition consiste en la même séquence de tokens (généralement, apparaît dans le même en-tête).
- La recherche de nom à partir de chaque définition trouve les mêmes entités (après résolution de surcharge ), sauf que :
-
- Les constantes avec liaison interne ou sans liaison peuvent faire référence à différents objets tant qu'elles ne sont pas odr-utilisées et ont les mêmes valeurs dans chaque définition.
|
(depuis C++11) |
- Les opérateurs surchargés, y compris les fonctions de conversion, d'allocation et de désallocation, se réfèrent à la même fonction dans chaque définition (sauf s'ils se réfèrent à une fonction définie dans la définition même).
- Les entités correspondantes ont la même liaison de langage dans chaque définition (par exemple, le fichier d'en-tête n'est pas à l'intérieur d'un bloc extern "C" ).
-
Si un objet
constest initialisé de manière constante dans l'une des définitions, il est initialisé de manière constante dans chaque définition. - Les règles ci-dessus s'appliquent à chaque argument par défaut utilisé dans chaque définition.
- Si la définition concerne une classe avec un constructeur implicitement déclaré, chaque unité de traduction où elle est odr-utilisée doit appeler le même constructeur pour la base et les membres.
|
(depuis C++20) |
- Si la définition concerne un template, alors toutes ces exigences s'appliquent à la fois aux noms au point de définition et aux noms dépendants au point d'instanciation.
Si toutes ces exigences sont satisfaites, le programme se comporte comme s'il n'y avait qu'une seule définition dans l'ensemble du programme. Sinon, le programme est mal formé, aucun diagnostic requis.
Note : en C, il n'y a pas de règle ODR à l'échelle du programme pour les types, et même les déclarations externes de la même variable dans différentes unités de traduction peuvent avoir des types différents tant qu'ils sont compatibles . En C++, les tokens du code source utilisés dans les déclarations du même type doivent être identiques comme décrit ci-dessus : si un fichier .cpp définit struct S { int x ; } ; et qu'un autre fichier .cpp définit struct S { int y ; } ; , le comportement du programme qui les lie ensemble est indéfini. Ce problème est généralement résolu avec des espaces de noms anonymes .
Nommer une entité
Une variable est nommée par une expression si l'expression est une expression d'identifiant qui la désigne.
Une fonction est nommée par une expression ou une conversion dans les cas suivants :
- Une fonction dont le nom apparaît comme une expression ou une conversion (y compris une fonction nommée, un opérateur surchargé, une conversion définie par l'utilisateur , les formes de placement définies par l'utilisateur de operator new , l'initialisation non par défaut) est nommée par cette expression si elle est sélectionnée par la résolution de surcharge, sauf lorsqu'il s'agit d'une fonction membre virtuelle pure non qualifiée ou d'un pointeur-vers-membre vers une fonction virtuelle pure.
- Une fonction d' allocation ou de désallocation pour une classe est nommée par une expression new apparaissant dans une expression.
- Une fonction de désallocation pour une classe est nommée par une expression delete apparaissant dans une expression.
- Un constructeur sélectionné pour copier ou déplacer un objet est considéré comme étant nommé par l'expression ou la conversion même si une élision de copie a lieu. L'utilisation d'une prvalue dans certains contextes ne copie ni ne déplace un objet, voir l'élision obligatoire . (depuis C++17)
Une expression potentiellement évaluée ou une conversion utilise odr une fonction si elle la nomme.
|
Une expression ou conversion potentiellement évaluée de manière constante qui nomme une fonction constexpr la rend nécessaire pour l'évaluation constante , ce qui déclenche la définition d'une fonction par défaut ou l'instanciation d'une spécialisation de modèle de fonction, même si l'expression n'est pas évaluée. |
(since C++11) |
Résultats potentiels
L'ensemble des résultats potentiels d'une expression E est un ensemble (éventuellement vide) d'expressions d'identifiants qui apparaissent dans E , combinés comme suit :
- Si E est une expression d'identifiant , l'expression E est son seul résultat potentiel.
- Si E est une expression d'indice ( E1 [ E2 ] ) où l'un des opérandes est un tableau, les résultats potentiels de cet opérande sont inclus dans l'ensemble.
- Si E est une expression d'accès à un membre de classe de la forme E1. E2 ou E1. template E2 nommant un membre de données non statique, les résultats potentiels de E1 sont inclus dans l'ensemble.
- Si E est une expression d'accès à un membre de classe nommant un membre de données statique, l'expression d'identifiant désignant le membre de données est incluse dans l'ensemble.
- Si E est une expression d'accès à un membre via un pointeur de la forme E1. * E2 ou E1. * template E2 dont le second opérande est une expression constante, les résultats potentiels de E1 sont inclus dans l'ensemble.
- Si E est une expression entre parenthèses ( ( E1 ) ), les résultats potentiels de E1 sont inclus dans l'ensemble.
- Si E est une expression conditionnelle glvalue ( E1 ? E2 : E3 , où E2 et E3 sont des glvalues), l'union des résultats potentiels de E2 et E3 sont tous deux inclus dans l'ensemble.
- Si E est une expression virgule ( E1, E2 ), les résultats potentiels de E2 sont dans l'ensemble des résultats potentiels.
- Sinon, l'ensemble est vide.
Utilisation ODR (définition informelle)
Un objet est odr-utilisé si sa valeur est lue (sauf s'il s'agit d'une constante évaluée à la compilation) ou écrite, si son adresse est prise, ou si une référence lui est liée,
Une référence est odr-utilisée si elle est utilisée et que son référent n'est pas connu à la compilation,
Une fonction est odr-utilisée si un appel de fonction lui est effectué ou si son adresse est prise.
Si une entité est odr-utilisée, sa définition doit exister quelque part dans le programme ; une violation de cela est généralement une erreur de liaison.
struct S { static const int x = 0; // membre de données statique // une définition en dehors de la classe est requise s'il est odr-utilisé }; const int& f(const int& r); int n = b ? (1, S::x) // S::x n'est pas odr-utilisé ici : f(S::x); // S::x est odr-utilisé ici : une définition est requise
Utilisation ODR (définition formelle)
Une variable
x
qui est nommée par une
expression potentiellement évaluée
expr
qui apparaît à un point
P
est odr-utilisée par
expr
, sauf si l'une des conditions suivantes est satisfaite :
-
x
est une référence qui est
utilisable dans les expressions constantes
à
P. -
x
n'est pas une référence et
(jusqu'à C++26)
expr
est un élément de l'ensemble des résultats potentiels d'une expression
E
, et l'une des conditions suivantes est satisfaite :
- E est une expression à valeur ignorée , et aucune conversion lvalue-vers-rvalue ne lui est appliquée.
-
x
est un
objet non volatile
(depuis C++26)
qui est utilisable dans les expressions constantes à
Pet n'a pas de sous-objets mutables, et l'une des conditions suivantes est satisfaite :
|
(depuis C++26) |
-
-
- E a un type non-classe non qualifié volatile, et la conversion lvalue-vers-rvalue lui est appliquée.
-
struct S { static const int x = 1; }; // application de la conversion lvalue-vers-rvalue // à S::x produit une expression constante int f() { S::x; // l'expression de valeur ignorée n'utilise pas S::x par ODR return S::x; // l'expression où la conversion lvalue-vers-rvalue // s'applique n'utilise pas S::x par ODR }
* this est odr-utilisé si this apparaît comme une expression potentiellement évaluée (incluant le this implicite dans une expression d'appel de fonction membre non statique).
|
Une liaison structurée est utilisée comme ODR si elle apparaît comme une expression potentiellement évaluée. |
(depuis C++17) |
Une fonction est odr-utilisée dans les cas suivants :
- Une fonction est odr-utilisée si elle est nommée par (voir ci-dessous) une expression ou conversion potentiellement évaluée.
- Une fonction membre virtuelle est odr-utilisée si elle n'est pas une fonction membre virtuelle pure (les adresses des fonctions membres virtuelles sont nécessaires pour construire la vtable).
- Une fonction d'allocation ou de désallocation non-placement pour une classe est odr-utilisée par la définition d'un constructeur de cette classe.
- Une fonction de désallocation non-placement pour une classe est odr-utilisée par la définition du destructeur de cette classe, ou en étant sélectionnée par la recherche au point de définition d'un destructeur virtuel.
-
Un opérateur d'affectation dans une classe
Tqui est membre ou base d'une autre classeUest odr-utilisé par les fonctions de copie-affectation ou déplacement-affectation implicitement définies deU. - Un constructeur (y compris les constructeurs par défaut) pour une classe est odr-utilisé par l'initialisation qui le sélectionne.
- Un destructeur pour une classe est odr-utilisé s'il est potentiellement invoqué .
|
Cette section est incomplète
Motif : liste de toutes les situations où l'utilisation odr fait une différence |
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 | Applicable à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 261 | C++98 |
une fonction de désallocation pour une classe polymorphe
pouvait être odr-utilisée même sans expressions new ou delete pertinentes dans le programme |
complété les cas d'odr-utilisation
pour couvrir les constructeurs et destructeurs |
| CWG 678 | C++98 |
une entité pouvait avoir des définitions
avec différentes liaisons de langage |
le comportement est
indéfini dans ce cas |
| CWG 1472 | C++98 |
les variables référence satisfaisant les exigences pour
apparaître dans une expression constante étaient odr-utilisées même si la conversion lvalue-vers-rvalue est immédiatement appliquée |
elles ne sont pas
odr-utilisées dans ce cas |
| CWG 1614 | C++98 | prendre l'adresse d'une fonction virtuelle pure l'odr-utilisait | la fonction n'est pas odr-utilisée |
| CWG 1741 | C++98 |
les objets constants immédiatement convertis lvalue-vers-rvalue
dans des expressions potentiellement évaluées étaient odr-utilisés |
ils ne sont pas odr-utilisés |
| CWG 1926 | C++98 | les expressions d'indice de tableau ne propageaient pas les résultats potentiels | elles propagent |
| CWG 2242 | C++98 |
il n'était pas clair si un objet
const
seulement
initialisé-constant dans certaines de ses définitions viole ODR |
ODR n'est pas violé ; l'objet est
initialisé-constant dans ce cas |
| CWG 2300 | C++11 |
les expressions lambda dans différentes unités
de traduction ne pouvaient jamais avoir le même type de fermeture |
le type de fermeture peut être le
même selon la règle de définition unique |
| CWG 2353 | C++98 |
un membre de données statique n'était pas un résultat potentiel
d'une expression d'accès membre y accédant |
il l'est |
| CWG 2433 | C++14 |
un modèle de variable ne pouvait pas avoir
de définitions multiples dans un programme |
il peut |
Références
- Norme C++23 (ISO/CEI 14882:2024) :
-
- 6.3 Règle de définition unique [basic.def.odr]
- Norme C++20 (ISO/CEI 14882:2020) :
-
- 6.3 Règle de définition unique [basic.def.odr]
- Norme C++17 (ISO/CEI 14882:2017) :
-
- 6.2 Règle de définition unique [basic.def.odr]
- Norme C++14 (ISO/IEC 14882:2014) :
-
- 3.2 Règle de définition unique [basic.def.odr]
- Norme C++11 (ISO/CEI 14882:2011) :
-
- 3.2 Règle de définition unique [basic.def.odr]
- Norme C++03 (ISO/IEC 14882:2003) :
-
- 3.2 Règle de définition unique [basic.def.odr]
- Norme C++98 (ISO/CEI 14882:1998) :
-
- 3.2 Règle de définition unique [basic.def.odr]