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ă:
- Î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ță.
- Î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.
- 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.