Espacios de nombres
Variantes
Acciones

Definiciones y ODR(regla de una definición)

De cppreference.com
< cpp‎ | language

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); // se declara, pero no se define f
extern const int a; // se declara, pero no se define a
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
  • (obsoleto) 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.

[editar] 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).

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.

Se requiere que aparezca una sola definición de una clase en cualquier unidad de traducción donde se use de manera que debe ser 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.

[editar] 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 requiera una definición fuera de la clase si se usa odr
};
const int& f(const int& r);
 
int n = b ? (1, S::x) // S::x no es de uso odr aquí
          : f(S::x);  // S::x es de uso odr aquí: se requiere un definición

Formalmente,

1) una variable x en una expresión potencialmente evaluada ex es de uso odr, a menos que las siguientes dos condiciones sean verdaderas:
  • la aplicación de la conversión de lvalue a rvalue de x resulta en una expresión constante que no invoca funciones no triviales
  • o bien x no es un objeto (es decir, x es una referencia), o, si x es un objeto, es uno de los potenciales resultados de una expresión más grande e, donde esa expresión es una expresión de valor descartado o tiene aplicada una conversión de lvalue a rvalue
struct S { static const int x = 1; }; // aplicar la conversión de lvalue a rvalue a S::x 
                                      // da como resultado una expresión constante
int f() { 
    S::x;        // expresión de valor descartado no usa odr S::x
    return S::x; // expresión donde la conversión de lvalue a rvalue no usa odr S::x
}
2) *this es de uso odr si aparece this como una expresión potencialmente evaluada (incluyendo el this implícito en una expresión de llamada a una función miembro no estática)
3) Un vínculo estructurado es de uso odr si aparece en una expresión potencialmente evaluada
(desde C++17)

En las definiciones anteriores, potencialmente evaluado significa que la expresión no es un operando no evaluable (o su subexpresión), como el operando de sizeof y un conjunto de resultados potenciales de una expresión e es un conjunto (que puede estar vacío) de expresiones de identificación que aparecen dentro de e, combinadas de la siguiente manera:

  • Si e es una expresión de subíndice de matriz (e1[e2]) donde uno de los operandos es una matriz, los resultados potenciales de ese operando se incluyen en el conjunto
(desde C++17)
  • Si e es una expresión de acceso a miembro de clase (e1.e2 o e1->e2), los resultados potenciales de la expresión del objeto e1 se incluye en el conjunto.
  • Si e es una expresión de acceso de puntero a miembro (e1.*e2 o e1->*e2) cuyo segundo operando es una expresión constante, los resultados potenciales de la expresión de objeto e1 están incluidos en el conjunto.
  • Si e es una expresión entre paréntesis ((e1)), los resultados potenciales de e1 están incluidos en el conjunto.
  • Si e es una expresión glvalue condicional (e1?e2:e3, donde e2 y e3 son glvalues), la unión de los resultados potenciales de e2 y e3 está incluidos en el conjunto.
  • Si e es una expresión con coma (e1,e2), los resultados potenciales de e2 están en el conjunto de resultados posibles.
  • En otro caso, el conjunto está vació.
struct S {
  static const int a = 1;
  static const int b = 2;
};
int f(bool x) {
  return x ? S::a : S::b;
  // x es una parte de la subexpresión "x" (a la izquierda de ?),
  // que realiza una conversión de lvalue a rvalue, por lo que no es de uso odr
  // S::a y S::b son lvalues, y se transfiere como ''resultados potenciales'' al resultado 
  // del glvalue condicional
  // Este resultado está sujeto a la conversión de lvalue a rvalue solicitada
  // para iniciar la copia del valor retornado, por lo que S::a y S::b no son de uso odr
}
4) Las funciones son de uso ODR si
  • Una función cuyo nombre se muestra como una expresión potencialmente evaluada (incluyendo la función nombrada, operador sobrecargado, conversión definida por usuario, las formas de colocación definidas por el usuario del operador new, inicialización no predeterminada) es de uso odr si se selecciona para la resolución de sobrecarga, excepto cuando sea una función miembro virtual pura sin calificar o un puntero a miembro a una función virtual pura (desde C++17).
  • Una función miembro virtual es de uso odr si no es una función miembro virtual pura (se requieren las direcciones de las funciones miembro virtuales para crear la tabla virtual)
  • Una función de asignación o desasignación para una clase es de uso odr para una expresión new que aparece en una expresión potencialmente evaluada.
  • Una función de desasignación para una clase es de uso odr para una expresión delete que aparece en una expresión potencialmente evaluada.
  • Una función de asignación o desasignación sin ubicación para una clase es de uso odr para la definición de un constructor de esa clase.
  • Una función de desasignación sin ubicar para una clase es de uso odr para la definición del destructor de la clase, o al ser seleccionado por la búsqueda en el punto de la definición de un destructor virtual
  • Un operador de asignación en una clase T que es miembro o base de otra clase U es de uso odr para funciones de asignación de copia o movimiento de U implícitamente definidas.
  • Un constructor (incluyendo constructores predeterminados) para una clase es uso odr para la inicialización que la selecciona.
  • Un destructor para una clase es de uso odr si se invoca potencialmente.
En todos los casos, un constructor seleccionado para copiar o mover un objeto es de uso odr incluso si tiene lugar una copia elision.

[editar] Referencias

  • C++11 standard (ISO/IEC 14882:2011):
  • 3.2 One definition rule [basic.def.odr]