Definiciones y regla de una definición (ODR)
Las definiciones son declaraciones que definen completamente la entidad introducida por la declaración. Cada declaración es una definición, excepto en los siguientes casos:
- Una declaración de función sin el cuerpo
int f(int); // f se declara, pero no se define
- Cualquier declaración con un especificador de clase de almacenamiento o con un especificador de enlace del lenguaje (como
extern "C") sin un inicializador
extern const int a; // a se declara, pero no se define
extern const int b = 1; // se define b
- Declaración de un miembro estático de datos no inline (desde C++17) dentro de la definición de una clase
struct S {
int n; // define S::n
static int i; // declara, pero no define S::i
inline static int x; // define S::x
}; // define S
int S::i; // define S::i
struct S {
static constexpr int x = 42; // implícitamente inline, define S::x
};
constexpr int S::x; // declara S::x, no es una redefinición
|
(desde C++17) |
- Declaración de un nombre de clase (mediante declaración de prototipo o el uso del especifícador de tipo elaborado en otra declaración).
struct S; // declara, pero no define S
class Y f(class T p); // declara, pero no define Y y T (y también f y p)
enum Color : int; // declara, pero no define Color
|
(desde C++11) |
- Declaración de un parámetro de plantilla
template<typename T> // declara, pero no define T
- Declaración de un parámtero en la declaración de una función sin definición
int f(int x); // declara, pero no define f ni x
int f(int x) { // define f y x
return x+a;
}
- Una declaración typedef
typedef S S2; // declara, pero no define S2 (S puede estar incompleto)
using S2 = S; // declara, pero no define S2 (S puede estar incompleto)
|
(desde C++11) |
using N::d; // declara, pero no define d
|
(desde C++17) |
|
(desde C++11) |
- Una declaración vacía (no se define ninguna entidad)
- Una directiva using (no se define ninguna entidad)
extern template f<int, char>; // declara, pero no define f<int, char>
|
(desde C++11) |
- Una especialización explícita cuya declaración no es una definición.
template<> struct A<int>; // declara, pero no define A<int>
Una declaración asm no define ninguna entidad, pero se clasifica como definición.
Cuando es necesario, el compilador puede definir implícitamente el constructor por defecto, constructor copia, constructor de movimiento, operador de asignación de copia, operador de asignación de movimiento, y el destructor.
Si la definición de un objeto da como resultado un objeto de un tipo incompleto, el programa es erróneo.
Regla de una definición (ODR)
Solamente se permite una definición de cualquier variable, función, tipo clase, tipo enumeración, concepto (desde C++20) o plantilla en una unidad de traducción (pueden haber múltiples declaraciones, pero una única definición). El acrónimo ODR proviene del inglés One Definition Rule.
Una y sólo una definición para toda función no inline o variable que es de uso ODR (ver más abajo) se permite que aparezca en todo el programa (incluyendo cualquier biblioteca estándar o definida por usuario). No se requiere que el compilador compruebe esta violación, pero en este caso el comportamiento del programa es indeterminado.
Para una función inline o variable inline (desde C++17), se requiere una definición en cada unidad de traducción donde sea de uso odr.
Para una clase, se requiere una definición siempre que la clase se use de una manera que requiera que sea completa.
Puede haber más de una definición en un programa, siempre y cuando cada definición aparezca en una unidad de traducción diferente, de cada uno de los siguientes elementos: tipo de clase, tipo enumeración, función inline con enlace externo, variable inline con enlace externo (desde C++17), plantilla de clase, plantilla de función no estática, dato miembro estático de una plantilla de clase, función miembro de una plantilla de clase, especialización parcial de plantilla, concepto (desde C++20), siempre que sea verdadero lo siguiente:
- cada definición consiste en la misma secuencia de elementos (normalmente, aparecen en el mismo archivo de cabecera)
- la búsqueda de nombres dentro de cada definición encuentra las mismas entidades (después de la resolución de sobrecarga), excepto que las constantes con enlaces internos o sin vinculación pueden referirse a objetos diferentes siempre que no se utilice ODR y tengan los mismos valores en cada definición.
- los operadores sobrecargados, incluidas las funciones de conversión, asignación y desasignación, hacen referencia a la misma función de cada definición (a menos que se refiera a una definida en la definición).
- el enlazado del lenguaje es el mismo (por ejemplo, el archivo incluido no está dentro de un bloque "C" externo)
- las tres reglas anteriores se aplican a cada argumento predeterminado utilizado en cada definición
|
(desde C++20) |
- si la definición es para una clase con un constructor declarado implícitamente, cada unidad de traducción donde se usa ODR debe llamar al mismo constructor para la base y los miembros
- si la definición es para una plantilla, todos estos requisitos se aplican a los nombres en el punto de definición y los nombres dependientes en el punto de creación de instancias.
Si se satisfacen todas la condiciones, el programa se comporta como si sólo hubiera una definición en todo el programa. En otro caso, el comportamiento es indeterminado.
Nota: en C, no existe un ODR en todo el programa para tipos, e incluso las declaraciones externas de la misma variable en diferentes unidades de traducción pueden tener diferentes tipos siempre que sean compatibles. En C ++, los elementos del código fuente utilizados en las declaraciones del mismo tipo deben ser los mismos que los descritos anteriormente: si un archivo .cpp define struct S {int x; }; y otro archivo .cpp define struct S {int y; };, el comportamiento del programa que los une no está determinado. Esto generalmente se resuelve con espacios de nombres anónimos.
Uso ODR
Informalmente, un objeto es de uso ODR cuando su valor se lee (a menos que sea una constante en tiempo de compilación) o se escribe, se tome su dirección o se vincule con una referencia; se usa una referencia ODR si se usa y su referente no se conoce en tiempo de compilación; y se utiliza una función ODR si se realiza una llamada a función o se toma su dirección. Si se utiliza un objeto, una referencia o una función ODR, su definición debe existir en algún lugar del programa; una violación de esta condición suele ser un error en tiempo de enlazado.
struct S {
static const int x = 0; // miembro de datos estático
// se requiere una definición fuera de la clase si tiene uso ODR
};
const int& f(const int& r);
int n = b ?