Espacios de nombres
Variantes

Definiciones y regla de una definición (ODR)

De cppreference.com

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
extern const int a; // a se declara, pero no se define
extern const int b = 1; // se define b
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
  • (en desuso) Declaración de un miembro estático de datos en el ámbito de un espacio de nombres que se define en una clase con el especificador constexpr
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)
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;
}
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)
extern template f<int, char>; // declara, pero no define f<int, char>
(desde C++11)
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
  • si la definición invoca una función con una condición previa ([[expects:]]) o es una función que contiene una aserción ([[ assert:]]) o tiene una condición de declaración ([[expects:]] o [[ensures:]]), está definido por la implementación bajo qué condiciones todas las definiciones deben traducirse utilizando el mismo nivel de compilación y el modo de continuación en una violació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 ?