Definitions and ODR (One Definition Rule)
Definitions sind declarations , die die durch die Deklaration eingeführte Entität vollständig definieren. Jede Deklaration ist eine Definition, mit Ausnahme der folgenden:
- Eine Funktionsdeklaration ohne Funktionsrumpf:
int f(int); // deklariert, aber definiert f nicht
- Jede Deklaration mit einem extern Speicherklassenspezifizierer oder mit einer Sprachbindungsspezifikation (wie extern "C" ) ohne Initialisierer:
extern const int a; // deklariert, aber definiert a nicht extern const int b = 1; // definiert b
- Deklaration eines non-inline (since C++17) static data member innerhalb einer Klassendefinition:
struct S { int n; // definiert S::n static int i; // deklariert, aber definiert nicht S::i inline static int x; // definiert S::x }; // definiert S int S::i; // definiert S::i
struct S { static constexpr int x = 42; // implizit inline, definiert S::x }; constexpr int S::x; // deklariert S::x, keine Neudefinition |
(seit C++17) |
- Deklaration eines Klassennamens (durch forward declaration oder durch Verwendung des elaborated type specifier in einer anderen Deklaration):
struct S; // deklariert, aber definiert S nicht class Y f(class T p); // deklariert, aber definiert Y und T nicht (und auch f und p nicht)
enum Color : int; // declares, but does not define Color |
(seit C++11) |
- Deklaration eines Template-Parameters :
template<typename T> // deklariert, aber definiert T nicht
- Eine Parameterdeklaration in einer Funktionsdeklaration, die keine Definition ist:
int f(int x); // deklariert, aber definiert f und x nicht int f(int x) // definiert f und x { return x + a; }
- Eine typedef Deklaration:
typedef S S2; // deklariert, aber definiert S2 nicht (S kann unvollständig sein)
using S2 = S; // declares, but does not define S2 (S may be incomplete) |
(seit C++11) |
- Eine using-declaration :
using N::d; // deklariert, aber definiert d nicht
|
(seit C++17) |
|
(seit C++11) |
- Eine leere Deklaration (definiert keine Entitäten)
- Eine using-Direktive (definiert keine Entitäten)
extern template f<int, char>; // declares, but does not define f<int, char> |
(seit C++11) |
- Eine explizite Spezialisierung deren Deklaration keine Definition ist:
template<> struct A<int>; // deklariert, aber definiert nicht A<int>
Eine asm-Deklaration definiert keine Entitäten, wird jedoch als Definition klassifiziert.
Falls erforderlich, kann der Compiler implizit den default constructor , copy constructor , move constructor , copy assignment operator , move assignment operator , und den destructor definieren.
Wenn die Definition eines Objekts zu einem Objekt vom incomplete type oder abstract class type führt, ist das Programm fehlerhaft.
Inhaltsverzeichnis |
One-Definition-Regel
Nur eine Definition einer beliebigen Variable, Funktion, Klassentyp, Enumerationstyp , concept (since C++20) oder Template ist in einer Übersetzungseinheit erlaubt (einige davon können mehrere Deklarationen haben, aber nur eine Definition ist erlaubt).
Genau eine Definition jeder nicht- inline Funktion oder Variable, die odr-used ist (siehe unten), muss im gesamten Programm (einschließlich aller Standard- und benutzerdefinierten Bibliotheken) vorhanden sein. Der Compiler ist nicht verpflichtet, diesen Verstoß zu diagnostizieren, aber das Verhalten des Programms, das dagegen verstößt, ist undefiniert.
Für eine Inline-Funktion oder Inline-Variable (since C++17) ist eine Definition in jeder Übersetzungseinheit erforderlich, in der sie odr-used ist.
Für eine Klasse ist eine Definition erforderlich, wo immer die Klasse auf eine Weise verwendet wird, die erfordert, dass sie vollständig ist.
In einem Programm kann es mehr als eine Definition von jedem der Folgenden geben: Klassentyp, Aufzählungstyp, Inline-Funktion , Inline-Variable (seit C++17) , templated entity (Template oder Mitglied eines Templates, aber keine vollständige template specialization ), solange alle folgenden Bedingungen erfüllt sind:
- Jede Definition erscheint in einer anderen Übersetzungseinheit.
|
(seit C++20) |
- Jede Definition besteht aus der gleichen Abfolge von Tokens (typischerweise erscheint in derselben Header-Datei).
- Die Namenssuche innerhalb jeder Definition findet dieselben Entitäten (nach Overload Resolution ), außer dass:
-
- Konstanten mit internem oder ohne Verknüpfung können auf verschiedene Objekte verweisen, solange sie nicht odr-used sind und in jeder Definition dieselben Werte haben.
|
(seit C++11) |
- Überladene Operatoren, einschließlich Konvertierungs-, Allokations- und Deallokationsfunktionen, beziehen sich in jeder Definition auf dieselbe Funktion (sofern sie nicht auf eine innerhalb der Definition definierte verweisen).
- Entsprechende Entitäten haben in jeder Definition dieselbe Sprachverknüpfung (z.B. befindet sich die Includedatei nicht innerhalb eines extern "C" Blocks).
-
Wenn ein
const-Objekt in einer der Definitionen konstant-initialisiert wird, wird es in jeder Definition konstant-initialisiert. - Die obigen Regeln gelten für jedes Standardargument, das in jeder Definition verwendet wird.
- Wenn die Definition für eine Klasse mit einem implizit deklarierten Konstruktor ist, muss jede Übersetzungseinheit, in der sie odr-used wird, denselben Konstruktor für Basis und Member aufrufen.
|
(seit C++20) |
- Wenn die Definition für ein Template gilt, dann gelten alle diese Anforderungen sowohl für Namen am Punkt der Definition als auch für abhängige Namen am Punkt der Instanziierung.
Wenn alle diese Anforderungen erfüllt sind, verhält sich das Programm so, als ob es nur eine Definition im gesamten Programm gäbe. Andernfalls ist das Programm fehlerhaft, keine Diagnose erforderlich.
Hinweis: In C gibt es keine programmweite ODR für Typen, und sogar externe Deklarationen derselben Variable in verschiedenen Übersetzungseinheiten können unterschiedliche Typen haben solange sie kompatibel sind . In C++ müssen die im Quellcode verwendeten Tokens für Deklarationen desselben Typs identisch sein, wie oben beschrieben: wenn eine .cpp-Datei struct S { int x ; } ; definiert und eine andere .cpp-Datei struct S { int y ; } ; definiert, ist das Verhalten des Programms, das sie zusammenlinkt, undefiniert. Dies wird üblicherweise mit unbenannten Namensräumen gelöst.
Benennung einer Entität
Eine Variable wird benannt durch einen Ausdruck, wenn der Ausdruck ein Identifikatorausdruck ist, der sie bezeichnet.
Eine Funktion wird in folgenden Fällen durch einen Ausdruck oder eine Konvertierung benannt :
- Eine Funktion, deren Name als Ausdruck oder Konvertierung erscheint (einschließlich benannter Funktion, überladener Operatoren, benutzerdefinierter Konvertierung , benutzerdefinierter Platzierungsformen von operator new , nicht-standardmäßiger Initialisierung) wird durch diesen Ausdruck benannt, wenn sie durch Überladungsauflösung ausgewählt wird, außer wenn es sich um eine unqualifizierte rein virtuelle Memberfunktion oder einen Zeiger-auf-Member zu einer rein virtuellen Funktion handelt.
- Eine Allokierungs- oder Deallokierungsfunktion für eine Klasse wird durch einen new-Ausdruck benannt, der in einem Ausdruck erscheint.
- Eine Deallokierungsfunktion für eine Klasse wird durch einen delete-Ausdruck benannt, der in einem Ausdruck erscheint.
- Ein Konstruktor, der zum Kopieren oder Verschieben eines Objekts ausgewählt wird, wird als durch den Ausdruck oder die Konvertierung benannt betrachtet, selbst wenn Kopierelision stattfindet. Die Verwendung eines Prvalue in bestimmten Kontexten kopiert oder verschiebt kein Objekt, siehe obligatorische Elision . (seit C++17)
Ein potenziell ausgewerteter Ausdruck oder eine Konvertierung verwendet eine Funktion im ODR-Sinne, wenn er sie benennt.
|
Ein potenziell konstant ausgewerteter Ausdruck oder eine Konvertierung, die eine constexpr-Funktion benennt, macht sie für die konstante Auswertung benötigt , was die Definition einer default-Funktion oder die Instanziierung einer Funktions-Template-Spezialisierung auslöst, selbst wenn der Ausdruck nicht ausgewertet wird. |
(since C++11) |
Mögliche Ergebnisse
Die Menge der potenziellen Ergebnisse eines Ausdrucks E ist eine (möglicherweise leere) Menge von Identifikator-Ausdrücken, die innerhalb von E vorkommen, und wie folgt kombiniert werden:
- Wenn E ein Identifikatorausdruck ist, ist der Ausdruck E sein einziges potentielles Ergebnis.
- Wenn E ein Indexausdruck ( E1 [ E2 ] ) ist, bei dem einer der Operanden ein Array ist, werden die potentiellen Ergebnisse dieses Operanden in die Menge aufgenommen.
- Wenn E ein Klassenmitglied-Zugriffsausdruck der Form E1. E2 oder E1. template E2 ist, der ein nicht-statisches Datenelement benennt, werden die potentiellen Ergebnisse von E1 in die Menge aufgenommen.
- Wenn E ein Klassenmitglied-Zugriffsausdruck ist, der ein statisches Datenelement benennt, wird der Identifikatorausdruck, der das Datenelement bezeichnet, in die Menge aufgenommen.
- Wenn E ein Zeiger-auf-Mitglied-Zugriffsausdruck der Form E1. * E2 oder E1. * template E2 ist, dessen zweiter Operand ein konstanter Ausdruck ist, werden die potentiellen Ergebnisse von E1 in die Menge aufgenommen.
- Wenn E ein Ausdruck in Klammern ( ( E1 ) ) ist, werden die potentiellen Ergebnisse von E1 in die Menge aufgenommen.
- Wenn E ein glvalue-Bedingungsausdruck ( E1 ? E2 : E3 , wobei E2 und E3 glvalues sind) ist, werden die Vereinigungen der potentiellen Ergebnisse von E2 und E3 beide in die Menge aufgenommen.
- Wenn E ein Kommaausdruck ( E1, E2 ) ist, sind die potentiellen Ergebnisse von E2 in der Menge der potentiellen Ergebnisse.
- Andernfalls ist die Menge leer.
ODR-Verwendung (informelle Definition)
Ein Objekt wird odr-used, wenn sein Wert gelesen (es sei denn, es handelt sich um eine Compile-Zeit-Konstante) oder geschrieben wird, seine Adresse genommen wird oder eine Referenz daran gebunden wird.
Eine Referenz wird odr-used, wenn sie verwendet wird und ihr Referent zur Kompilierzeit nicht bekannt ist.
Eine Funktion wird odr-used, wenn ein Funktionsaufruf an sie erfolgt oder ihre Adresse genommen wird.
Wenn eine Entität odr-used wird, muss ihre Definition irgendwo im Programm existieren; ein Verstoß dagegen ist normalerweise ein Linkzeitfehler.
struct S { static const int x = 0; // statisches Datenelement // eine Definition außerhalb der Klasse ist erforderlich, wenn es odr-used wird }; const int& f(const int& r); int n = b ? (1, S::x) // S::x wird hier nicht odr-used : f(S::x); // S::x wird hier odr-used: eine Definition ist erforderlich
ODR-Verwendung (formale Definition)
Eine Variable
x
, die durch einen
potenziell ausgewerteten Ausdruck
expr
benannt wird, der an einer Stelle
P
erscheint, wird durch
expr
odr-used, es sei denn, eine der folgenden Bedingungen ist erfüllt:
-
x
ist eine Referenz, die
in konstanten Ausdrücken verwendbar
ist bei
P. -
x
ist keine Referenz und
(bis C++26)
expr
ist ein Element der Menge potentieller Ergebnisse eines Ausdrucks
E
, und eine der folgenden Bedingungen ist erfüllt:
- E ist ein verworfen-wert Ausdruck , und keine Lvalue-zu-Rvalue-Konvertierung wird darauf angewendet.
-
x
ist ein
nicht-flüchtiges
(seit C++26)
Objekt, das in konstanten Ausdrücken verwendbar ist bei
Pund keine veränderlichen Unterobjekte besitzt, und eine der folgenden Bedingungen ist erfüllt:
|
(seit C++26) |
-
-
- E hat einen nicht-flüchtig qualifizierten Nicht-Klassentyp, und die Lvalue-zu-Rvalue-Konvertierung wird darauf angewendet.
-
struct S { static const int x = 1; }; // Anwendung der Lvalue-zu-Rvalue-Konvertierung // auf S::x ergibt einen konstanten Ausdruck int f() { S::x; // Ausdruck ohne Wertverwendung verwendet S::x nicht odr return S::x; // Ausdruck, bei dem Lvalue-zu-Rvalue-Konvertierung // angewendet wird, verwendet S::x nicht odr }
* this wird odr-verwendet, wenn this als potenziell ausgewerteter Ausdruck erscheint (einschließlich des impliziten this in einem Ausdruck für den Aufruf einer nicht-statischen Memberfunktion).
|
Eine structured binding ist odr-used, wenn sie als potenziell ausgewerteter Ausdruck erscheint. |
(since C++17) |
Eine Funktion wird in folgenden Fällen odr-used:
- Eine Funktion wird odr-used, wenn sie durch (siehe unten) einen potenziell ausgewerteten Ausdruck oder eine Konvertierung benannt wird.
- Eine virtuelle Memberfunktion wird odr-used, wenn sie keine pure virtuelle Memberfunktion ist (Adressen virtueller Memberfunktionen werden für die Konstruktion der vtable benötigt).
- Eine Nicht-Platzierungs-Allokations- oder Deallokationsfunktion für eine Klasse wird durch die Definition eines Konstruktors dieser Klasse odr-used.
- Eine Nicht-Platzierungs-Deallokationsfunktion für eine Klasse wird durch die Definition des Destruktors dieser Klasse odr-used, oder indem sie durch die Lookup-Operation an der Definitionsstelle eines virtuellen Destruktors ausgewählt wird.
-
Ein Zuweisungsoperator in einer Klasse
T, die Member oder Basis einer anderen KlasseUist, wird durch implizit definierte Copy-Assignment- oder Move-Assignment-Funktionen vonUodr-used. - Ein Konstruktor (einschließlich Standardkonstruktoren) für eine Klasse wird durch die Initialisierung , die ihn auswählt, odr-used.
- Ein Destruktor für eine Klasse wird odr-used, wenn er potenziell aufgerufen wird.
|
Dieser Abschnitt ist unvollständig
Grund: Auflistung aller Situationen, in denen ODR-Verwendung einen Unterschied macht |
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
|---|---|---|---|
| CWG 261 | C++98 |
eine Freigabefunktion für eine polymorphe Klasse
könnte odr-verwendet werden, selbst wenn keine relevanten new- oder delete-Ausdrücke im Programm vorhanden waren |
ergänzte die
ODR-Verwendungsfälle, um Konstruktoren und Destruktoren abzudecken |
| CWG 678 | C++98 |
eine Entität könnte Definitionen
mit unterschiedlichen Sprachverknüpfungen haben |
das Verhalten ist
in diesem Fall undefiniert |
| CWG 1472 | C++98 |
Referenzvariablen, die die Anforderungen für
das Erscheinen in einem konstanten Ausdruck erfüllen, wurden odr-verwendet, selbst wenn die Lvalue-zu-Rvalue-Konvertierung sofort angewendet wird |
sie werden in diesem Fall
nicht odr-verwendet |
| CWG 1614 | C++98 | die Adressnahme einer rein virtuellen Funktion verwendete sie odr | die Funktion wird nicht odr-verwendet |
| CWG 1741 | C++98 |
konstante Objekte, die sofort Lvalue-zu-Rvalue-
konvertiert in potenziell ausgewerteten Ausdrücken wurden, wurden odr-verwendet |
sie werden nicht odr-verwendet |
| CWG 1926 | C++98 | Array-Subscript-Ausdrücke propagierten keine potenziellen Ergebnisse | sie propagieren |
| CWG 2242 | C++98 |
es war unklar, ob ein
const
-Objekt, das nur
konstant-initialisiert in einem Teil seiner Definitionen ist, ODR verletzt |
ODR wird nicht verletzt; das Objekt ist
in diesem Fall konstant-initialisiert |
| CWG 2300 | C++11 |
Lambda-Ausdrücke in verschiedenen Übersetzungs-
einheiten konnten niemals denselben Closure-Typ haben |
der Closure-Typ kann unter
der One-Definition-Regel derselbe sein |
| CWG 2353 | C++98 |
ein statisches Datenelement war kein potenzielles Ergebnis
eines Member-Zugriffsausdrucks, der darauf zugreift |
es ist |
| CWG 2433 | C++14 |
eine Variablenvorlage konnte nicht
mehrere Definitionen in einem Programm haben |
sie kann |
Referenzen
- C++23-Standard (ISO/IEC 14882:2024):
-
- 6.3 One definition rule [basic.def.odr]
- C++20 Standard (ISO/IEC 14882:2020):
-
- 6.3 One definition rule [basic.def.odr]
- C++17-Standard (ISO/IEC 14882:2017):
-
- 6.2 One definition rule [basic.def.odr]
- C++14-Standard (ISO/IEC 14882:2014):
-
- 3.2 One definition rule [basic.def.odr]
- C++11 Standard (ISO/IEC 14882:2011):
-
- 3.2 One Definition Rule [basic.def.odr]
- C++03-Standard (ISO/IEC 14882:2003):
-
- 3.2 One definition rule [basic.def.odr]
- C++98-Standard (ISO/IEC 14882:1998):
-
- 3.2 One definition rule [basic.def.odr]