Regra de Uma Definição - One Definition Rule
A Regra de Uma Definição ( ODR ) é uma regra importante da linguagem de programação C ++ que prescreve que objetos e funções não embutidas não podem ter mais de uma definição em todo o programa e o modelo e os tipos não podem ter mais de uma definição por unidade de tradução . É definido na Norma ISO C ++ ( ISO / IEC 14882 ) 2003, na seção 3.2.
Resumo
Resumindo, o ODR afirma que:
- Em qualquer unidade de tradução, um modelo , tipo , função ou objeto não pode ter mais de uma definição. Alguns deles podem ter qualquer número de declarações. Uma definição fornece uma instância.
- Em todo o programa , um objeto ou função não embutida não pode ter mais de uma definição; se um objeto ou função é usado, deve ter exatamente uma definição. Você pode declarar um objeto ou função que nunca é usado e, nesse caso, você não precisa fornecer uma definição. Em nenhum caso pode haver mais de uma definição.
- Algumas coisas, como tipos, modelos e funções internas internas, podem ser definidas em mais de uma unidade de tradução. Para uma determinada entidade, cada definição deve ter a mesma sequência de tokens . Os objetos e funções não externos em unidades de tradução diferentes são entidades diferentes, mesmo que seus nomes e tipos sejam os mesmos.
Algumas violações do ODR devem ser diagnosticadas pelo compilador . Outras violações, especialmente aquelas que abrangem unidades de tradução, não precisam ser diagnosticadas.
Exemplos
Em geral, uma unidade de tradução deve conter no máximo uma definição de qualquer tipo de classe. Neste exemplo, duas definições do tipo de classe C ocorrem na mesma unidade de tradução . Isso normalmente ocorre se um arquivo de cabeçalho for incluído duas vezes pelo mesmo arquivo de origem sem os protetores de cabeçalho apropriados .
class C {}; // first definition of C
class C {}; // error, second definition of C
A seguir, formar um ponteiro para S ou definir uma função tomando uma referência para S são exemplos de construções legais, porque não exigem que o tipo de S seja completo . Portanto, uma definição não é necessária.
Definir um objeto do tipo S, uma função recebendo um argumento do tipo S ou usando S em uma expressão sizeof são exemplos de contextos em que S deve ser completo e, portanto, requerem uma definição.
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
Mais de uma definição
Em certos casos, pode haver mais de uma definição de um tipo ou modelo. Um programa que consiste em vários arquivos de cabeçalho e arquivos de origem normalmente terá mais de uma definição de um tipo, mas não mais de uma definição por unidade de tradução.
Se um programa contém mais de uma definição de um tipo, cada definição deve ser equivalente.
Definições de membros de dados const estáticos
No C ++ pré-padrão, todos os membros de dados estáticos exigiam uma definição fora de sua classe. No entanto, durante o processo de padronização C ++, decidiu-se levantar esse requisito para membros integrais const estáticos. A intenção era permitir usos como:
struct C {
static const int N = 10;
};
char data[C::N]; // N "used" without out-of-class definition
sem uma definição de escopo de namespace para N.
No entanto, a redação do padrão C ++ de 1998 ainda exigia uma definição se o membro fosse usado no programa. Isso incluía o membro aparecendo em qualquer lugar, exceto como o operando para sizeof ou typeid , tornando o item anterior malformado.
Isso foi identificado como um defeito, e a redação foi ajustada para permitir que tal membro apareça em qualquer lugar onde uma expressão constante seja necessária, sem exigir uma definição fora da classe. Isso inclui limites de matriz , expressões de caso , inicializadores de membro estático e argumentos de modelo sem tipo .
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
No entanto, o uso de um membro integral const estático em qualquer lugar, exceto onde uma expressão constante integral é necessária, requer uma definição:
struct C {
static const int N = 10;
};
int main() {
int i = C::N; // Ill-formed in C++03. Definition of C::N required.
}
Esse requisito foi relaxado em um padrão posterior, C ++ 11 .
Exemplo mostrando efeitos colaterais inesperados
Precisamos de 4 arquivos: "odr.h", "main.cpp", "odr1.cpp", "odr2.cpp"
A sigla "odr" aqui é a abreviação de "Regra de Uma Definição".
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();
}
Em um shell do Linux para experimentar, compile com:
g++ -c odr1.cpp g++ -c odr2.cpp g++ -c main.cpp g++ -o odr main.o odr1.o odr2.o
Em um "Prompt de comando das ferramentas de construção" do Windows Visual Studio, compile com:
cl /c main.cpp cl /c odr1.cpp cl /c odr2.cpp cl /Feodr.exe main.obj odr1.obj odr2.obj
Quando executado, a saída esperada é:
odr ONE dummy: Hello odr TWO dummy: World
Mas é muito provável que você obtenha:
odr ONE dummy: Hello odr ONE dummy: Hello
O problema é que o vinculador C ++ precisa descobrir como construir a tabela de método virtual para as (duas diferentes) classes "CDummy", e isso só funciona se os nomes das classes forem diferentes.