O regulă de definiție - One Definition Rule

Regula O definiție ( ODR ) este o regulă importantă a C ++ limbaj de programare , care prevede că obiectele și funcțiile de bază non-inline nu poate avea mai mult de o definiție în întregul program și șablon și tipuri nu pot avea mai mult de o definiție de unitate de traducere . Este definit în standardul ISO C ++ ( ISO / IEC 14882 ) 2003, la secțiunea 3.2.

rezumat

Pe scurt, ODR afirmă că:

  1. În orice unitate de traducere, un șablon , tip , funcție sau obiect nu poate avea mai mult de o definiție. Unele dintre acestea pot avea orice număr de declarații. O definiție oferă o instanță.
  2. În întregul program , un obiect sau o funcție non- linie nu poate avea mai multe definiții; dacă se folosește un obiect sau o funcție, acesta trebuie să aibă exact o definiție. Puteți declara un obiect sau o funcție care nu este niciodată folosit, caz în care nu trebuie să furnizați o definiție. În niciun caz nu pot exista mai multe definiții.
  3. Unele lucruri, cum ar fi tipurile, șabloanele și funcțiile externe inline, pot fi definite în mai multe unități de traducere. Pentru o entitate dată, fiecare definiție trebuie să aibă aceeași secvență de jetoane . Obiectele și funcțiile non-externe din diferite unități de traducere sunt entități diferite, chiar dacă numele și tipurile lor sunt aceleași.

Unele încălcări ale ODR trebuie diagnosticate de către compilator . Nu este necesară diagnosticarea altor încălcări, în special a celor care acoperă unitățile de traducere.

Exemple

În general, o unitate de traducere nu trebuie să conțină mai mult de o definiție a oricărui tip de clasă. În acest exemplu, două definiții ale clasei de tip C apar în aceeași unitate de traducere . Acest lucru se întâmplă de obicei dacă un fișier antet este inclus de două ori de același fișier sursă fără protecții antet adecvate .

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

În cele ce urmează, formarea unui pointer către S sau definirea unei funcții luând o referință la S sunt exemple de construcții juridice, deoarece acestea nu necesită ca tipul de S să fie complet . Prin urmare, nu este necesară o definiție.

Definirea unui obiect de tip S, o funcție care ia un argument de tip S sau utilizarea S într-o dimensiune a expresiei sunt exemple de contexte în care S trebuie să fie complet și, prin urmare, necesită o definiție.

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

Mai multe definiții

În anumite cazuri, poate exista mai mult de o definiție a unui tip sau a unui șablon. Un program format din mai multe fișiere antet și fișiere sursă va avea de obicei mai mult de o definiție a unui tip, dar nu mai mult de o definiție per unitate de traducere.

Dacă un program conține mai multe definiții ale unui tip, atunci fiecare definiție trebuie să fie echivalentă.

Definiții ale membrilor statici de date const

În C ++ pre-standard, toți membrii datelor statice au necesitat o definiție în afara clasei lor. Cu toate acestea, în timpul procesului de standardizare C ++ s-a decis ridicarea acestei cerințe pentru membrii integrali const constanți. Intenția a fost să permită utilizări precum:

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

fără o definiție a spațiului de nume pentru N.

Cu toate acestea, formularea standardului C ++ din 1998 necesită încă o definiție dacă membrul a fost utilizat în program. Aceasta a inclus membrul care apare oriunde, cu excepția ca operand la sizeof sau typeid , făcând efectiv mal -formatul de mai sus.

Acest lucru a fost identificat ca un defect, iar formularea a fost ajustată pentru a permite unui astfel de membru să apară oriunde este necesară o expresie constantă , fără a necesita o definiție în afara clasei. Aceasta include limitele matricei , expresiile de caz , inițializatoarele statice ale membrilor și argumentele șablonului nontype .

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

Cu toate acestea, utilizarea unui membru static const integral oriunde, cu excepția cazului în care este necesară o expresie constantă integrală, necesită o definiție:

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

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

Această cerință a fost relaxată într-un standard ulterior, C ++ 11 .

Exemplu care arată efecte secundare neașteptate

Avem nevoie de 4 fișiere: „odr.h”, „main.cpp”, „odr1.cpp”, „odr2.cpp”

Acronimul „odr” aici este prescurtarea pentru „Regula unei definiții”.

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();
}

Sub un shell Linux pentru a încerca, compilați cu:

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

În cadrul unui „Visual Prompt Command Command” din Windows Visual Studio, compilați cu:

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

Când este executat, ieșirea așteptată este:

odr ONE dummy: Hello
odr TWO dummy: World

Dar este foarte probabil să obțineți:

odr ONE dummy: Hello
odr ONE dummy: Hello

Problema este că linkerul C ++ trebuie să-și dea seama cum să construiască tabelul de metode virtuale pentru clasele (două diferite) „CDummy” și că funcționează numai dacă numele claselor sunt diferite.

Vezi si

Referințe