Eine Definitionsregel - One Definition Rule

Die One Definition Rule ( ODR ) ist eine wichtige Regel der Programmiersprache C++ , die vorschreibt, dass Objekte und Nicht-Inline-Funktionen nicht mehr als eine Definition im gesamten Programm und Template haben und Typen nicht mehr als eine Definition pro Übersetzungseinheit haben können . Es ist im ISO C++ Standard ( ISO/IEC 14882 ) 2003 in Abschnitt 3.2 definiert.

Zusammenfassung

Kurz gesagt heißt es in der ODR:

  1. In jeder Übersetzungseinheit kann eine Vorlage , ein Typ , eine Funktion oder ein Objekt nicht mehr als eine Definition haben. Einige davon können beliebig viele Deklarationen haben. Eine Definition stellt eine Instanz bereit.
  2. Im gesamten Programm kann ein Objekt oder eine Nicht- Inline-Funktion nicht mehr als eine Definition haben; Wenn ein Objekt oder eine Funktion verwendet wird, muss es genau eine Definition haben. Sie können ein Objekt oder eine Funktion deklarieren, die nie verwendet wird. In diesem Fall müssen Sie keine Definition angeben. In keinem Fall kann es mehr als eine Definition geben.
  3. Einige Dinge, wie Typen, Vorlagen und externe Inline-Funktionen, können in mehr als einer Übersetzungseinheit definiert werden. Für eine bestimmte Entität muss jede Definition dieselbe Tokensequenz aufweisen . Nicht-externe Objekte und Funktionen in verschiedenen Übersetzungseinheiten sind unterschiedliche Entitäten, auch wenn ihre Namen und Typen gleich sind.

Einige Verstöße gegen die ODR müssen vom Compiler diagnostiziert werden . Andere Verstöße, insbesondere solche, die Übersetzungseinheiten umfassen, müssen nicht diagnostiziert werden.

Beispiele

Im Allgemeinen darf eine Übersetzungseinheit nicht mehr als eine Definition eines beliebigen Klassentyps enthalten. In diesem Beispiel kommen zwei Definitionen des Klassentyps C in derselben Übersetzungseinheit vor . Dies tritt normalerweise auf, wenn eine Header-Datei zweimal von derselben Quelldatei ohne entsprechende Header-Guards eingeschlossen wird .

class C {}; // first definition of C
class C {}; // error, second definition of C

Im Folgenden sind das Bilden eines Zeigers auf S oder das Definieren einer Funktion, die eine Referenz auf S nimmt, Beispiele für legale Konstrukte, da sie nicht erfordern, dass der Typ von S vollständig ist . Eine Definition ist daher nicht erforderlich.

Das Definieren eines Objekts vom Typ S, eine Funktion, die ein Argument vom Typ S übernimmt, oder die Verwendung von S in einem sizeof- Ausdruck sind Beispiele für Kontexte, in denen S vollständig sein muss und daher eine Definition erfordert.

struct S;     // declaration of S
S * p;        // ok, no definition required
void f(S&);   // ok, no definition required
void f(S*);   // ok, no definition required 
S f();        // ok, no definition required - this is a function declaration only!

S s;          // error, definition required
sizeof(S);    // error, definition required

Mehr als eine Definition

In bestimmten Fällen kann es mehr als eine Definition eines Typs oder einer Vorlage geben. Ein Programm, das aus mehreren Header-Dateien und Quelldateien besteht, hat normalerweise mehr als eine Definition eines Typs, jedoch nicht mehr als eine Definition pro Übersetzungseinheit.

Wenn ein Programm mehr als eine Definition eines Typs enthält, muss jede Definition äquivalent sein.

Definitionen von statischen const-Datenmembern

In C++ vor dem Standard erforderten alle statischen Datenmember eine Definition außerhalb ihrer Klasse. Während des C++-Standardisierungsprozesses wurde jedoch beschlossen, diese Anforderung für statische const-Integralelemente aufzuheben. Die Absicht war, Verwendungen zu ermöglichen wie:

struct C {
  static const int N = 10;
};
char data[C::N]; // N "used" without out-of-class definition

ohne eine Namespace- Bereichsdefinition für N.

Trotzdem erforderte der Wortlaut des C++-Standards von 1998 immer noch eine Definition, wenn der Member im Programm verwendet wurde. Dies beinhaltete , dass das Element überall außer als Operand für sizeof oder typeid auftrat , was das Obige effektiv falsch formatierte .

Dies wurde als Mangel identifiziert und der Wortlaut angepasst, damit ein solches Mitglied überall dort erscheinen kann, wo ein konstanter Ausdruck erforderlich ist, ohne dass eine klassenfremde Definition erforderlich ist. Dazu gehören Array Grenzen, Fall Ausdrücke , statisches Element Initialisierungen und untypische Konstruktionen Vorlage Argumente .

struct C {
  static const int N = 10;
  static const int U = N; // Legal per C++03
};

char data[C::N]; // Legal per C++03

template<int> struct D;

template<> struct D<C::N> {}; // Legal per C++03

Die Verwendung eines statischen konstanten integralen Elements überall, außer wo ein ganzzahliger konstanter Ausdruck erforderlich ist, erfordert jedoch eine Definition:

struct C {
  static const int N = 10;
};

int main() {
  int i = C::N; // Ill-formed in C++03. Definition of C::N required.
}

Diese Anforderung wurde in einem späteren Standard, C++11, gelockert .

Beispiel mit unerwarteten Nebenwirkungen

Wir benötigen 4 Dateien: "odr.h", "main.cpp", "odr1.cpp", "odr2.cpp"

Das Akronym „odr“ steht hier für „One Definition Rule“.

odr.h:

// abstract base class
class CBase {
public:
	virtual void xxx() = 0;
	virtual ~CBase() = default;
};

extern CBase *odr1_create();
extern CBase *odr2_create();

main.cpp

#include "odr.h"

int main(int argc, char **argv)
{
	CBase *o1 = odr1_create();
	CBase *o2 = odr2_create();
	o1->xxx();
	o2->xxx();
}

odr1.cpp

#include <stdio.h>
#include "odr.h"

class CDummy : public CBase {
public:
	void xxx() override {
		printf("odr ONE dummy: Hello\n");
	}
};

CBase *odr1_create() {
	return new CDummy();
}

odr2.cpp

#include <stdio.h>
#include "odr.h"

class CDummy : public CBase {
public:
	void xxx() override {
		printf("odr TWO dummy: World\n");
	}
};

CBase *odr2_create() {
	return new CDummy();
}

Kompilieren Sie unter einer Linux-Shell zum Ausprobieren mit:

g++ -c odr1.cpp
g++ -c odr2.cpp
g++ -c main.cpp
g++ -o odr main.o odr1.o odr2.o

Kompilieren Sie unter einer Windows Visual Studio "Build Tools Command Prompt" mit:

cl /c main.cpp
cl /c odr1.cpp
cl /c odr2.cpp
cl /Feodr.exe main.obj odr1.obj odr2.obj

Bei der Ausführung ist die erwartete Ausgabe:

odr ONE dummy: Hello
odr TWO dummy: World

Aber Sie bekommen sehr wahrscheinlich:

odr ONE dummy: Hello
odr ONE dummy: Hello

Das Problem ist, dass der C++-Linker herausfinden muss, wie die virtuelle Methodentabelle für die (zwei verschiedenen) "CDummy"-Klassen erstellt wird, und das funktioniert nur, wenn die Klassennamen unterschiedlich sind.

Siehe auch

Verweise