Namespaces
Variants

Definitions and ODR (One Definition Rule)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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
extern const int a;     // deklariert, aber definiert a nicht
extern const int b = 1; // definiert b
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
  • (veraltet) Namespace-Bereich-Deklaration eines static-Datenmembers, der innerhalb der Klasse mit dem constexpr Spezifizierer definiert wurde:
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)
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;
}
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)
using N::d; // deklariert, aber definiert d nicht
(seit C++17)
(seit C++11)
extern template
f<int, char>; // declares, but does not define f<int, char>
(seit C++11)
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.
  • Lambda-Ausdrücke , die sich nicht in einem Standardargument oder einem Standard-Template-Argument (seit C++20) befinden, werden eindeutig durch die Sequenz von Tokens identifiziert, die zu ihrer Definition verwendet werden.
(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.
  • Wenn die Definition für eine Klasse mit einem defaulted three-way comparison ist, muss jede Übersetzungseinheit, in der sie odr-used wird, denselben Vergleichsoperator für Basisklassen 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 P und 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 Klasse U ist, wird durch implizit definierte Copy-Assignment- oder Move-Assignment-Funktionen von U odr-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.

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]