Definitions and ODR (One Definition Rule)
Definiciones son declaraciones que definen completamente la entidad introducida por la declaración. Toda declaración es una definición, excepto por las siguientes:
- Una declaración de función sin un cuerpo de función:
int f(int); // declara, pero no define f
- Cualquier declaración con un extern especificador de clase de almacenamiento o con un especificador de vinculación de lenguaje (como extern "C" ) sin un inicializador:
extern const int a; // declara, pero no define a extern const int b = 1; // define b
- Declaración de un no inline (desde C++17) static data member dentro de una definición de 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 una redefinición |
(desde C++17) |
- Declaración de un nombre de clase (mediante declaración anticipada o mediante el uso del especificador 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; // declares, but does not define Color |
(desde C++11) |
- Declaración de un template parameter :
template<typename T> // declara, pero no define T
- Una declaración de parámetro en una declaración de función que no es una definición:
int f(int x); // declara, pero no define f y x int f(int x) // define f y x { return x + a; }
- Una typedef declaración:
typedef S S2; // declara, pero no define S2 (S puede estar incompleto)
using S2 = S; // declares, but does not define S2 (S may be incomplete) |
(desde C++11) |
- Una using-declaration :
using N::d; // declara, pero no define d
|
(desde C++17) |
|
(desde C++11) |
- Una declaración vacía (no define ninguna entidad)
- Una directiva using (no define ninguna entidad)
extern template f<int, char>; // declares, but does not 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 una definición.
Donde sea necesario, el compilador puede definir implícitamente el default constructor , copy constructor , move constructor , copy assignment operator , move assignment operator , y el destructor .
Si la definición de cualquier objeto resulta en un objeto de tipo incomplete type o abstract class type , el programa está mal formado.
Contenidos |
Regla de Una Definición
Solo se permite una definición de cualquier variable, función, tipo de clase, tipo de enumeración , concept (since C++20) o plantilla en cualquier unidad de traducción (algunas de estas pueden tener múltiples declaraciones, pero solo se permite una definición).
Una y solo una definición de cada función o variable no inline que sea odr-used (ver abajo) debe aparecer en todo el programa (incluyendo cualquier biblioteca estándar y definida por el usuario). El compilador no está obligado a diagnosticar esta violación, pero el comportamiento del programa que la infringe es indefinido.
Para una función en línea o variable en línea (desde C++17) , se requiere una definición en cada unidad de traducción donde sea odr-used .
Para una clase, se requiere una definición dondequiera que la clase se utilice de una manera que requiera que sea completa .
Puede haber más de una definición en un programa de cada uno de los siguientes: tipo clase, tipo enumeración, función inline , variable inline (since C++17) , entidad plantilla (plantilla o miembro de plantilla, pero no especialización completa de plantilla ), siempre que se cumplan todas las siguientes condiciones:
- Cada definición aparece en una unidad de traducción diferente.
|
(desde C++20) |
- Cada definición consiste en la misma secuencia de tokens (típicamente, aparece en el mismo header).
- La búsqueda de nombres desde dentro de cada definición encuentra las mismas entidades (después de overload resolution ), excepto que:
-
- Las constantes con vinculación interna o sin vinculación pueden referirse a diferentes objetos siempre que no sean odr-usadas y tengan los mismos valores en cada definición.
|
(desde C++11) |
- Los operadores sobrecargados, incluyendo las funciones de conversión, asignación y liberación de memoria, se refieren a la misma función desde cada definición (a menos que se refieran a una definida dentro de la definición).
- Las entidades correspondientes tienen el mismo enlace de lenguaje en cada definición (por ejemplo, el archivo de inclusión no está dentro de un bloque extern "C" ).
-
Si un objeto
constes inicializado-constantemente en cualquiera de las definiciones, es inicializado-constantemente en cada definición. - Las reglas anteriores se aplican a cada argumento por defecto utilizado en cada definición.
- Si la definición es para una clase con un constructor declarado implícitamente, cada unidad de traducción donde es odr-usada debe llamar al mismo constructor para la base y los miembros.
|
(desde C++20) |
- Si la definición es para una plantilla, entonces todos estos requisitos se aplican tanto a los nombres en el punto de definición como a los nombres dependientes en el punto de instanciación.
Si todos estos requisitos se cumplen, el programa se comporta como si hubiera solo una definición en todo el programa. De lo contrario, el programa está mal formado, no se requiere diagnóstico.
Nota: en C, no existe una ODR (Definición Única) a nivel de programa para tipos, e incluso las declaraciones extern de la misma variable en diferentes unidades de traducción pueden tener tipos diferentes siempre que sean compatibles . En C++, los tokens del código fuente utilizados en las declaraciones del mismo tipo deben ser idénticos como se describe 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 enlaza queda indefinido. Esto normalmente se resuelve mediante espacios de nombres sin nombre .
Nombrar una entidad
Una variable es nombrada por una expresión si la expresión es una expresión de identificador que la denota.
Una función es nombrada por una expresión o conversión en los siguientes casos:
- Una función cuyo nombre aparece como una expresión o conversión (incluyendo función nombrada, operador sobrecargado, conversión definida por el usuario , formas de colocación definidas por el usuario de operator new , inicialización no predeterminada) es nombrada por esa expresión si es seleccionada por la resolución de sobrecarga, excepto cuando es una función miembro virtual pura no calificada o un puntero-a-miembro a una función virtual pura.
- Una función de asignación o desasignación para una clase es nombrada por una expresión new que aparece en una expresión.
- Una función de desasignación para una clase es nombrada por una expresión delete que aparece en una expresión.
- Un constructor seleccionado para copiar o mover un objeto se considera nombrado por la expresión o conversión incluso si ocurre elisión de copia . Usar un prvalue en algunos contextos no copia ni mueve un objeto, ver elisión obligatoria . (desde C++17)
Una expresión o conversión potencialmente evaluada utiliza odr a una función si la nombra.
|
Una expresión o conversión potencialmente evaluada como constante que nombra una función constexpr la hace necesaria para evaluación constante , lo que desencadena la definición de una función por defecto o la instanciación de una especialización de plantilla de función, incluso si la expresión no se evalúa. |
(since C++11) |
Resultados potenciales
El conjunto de resultados potenciales de una expresión E es un (posiblemente vacío) conjunto de expresiones de identificador que aparecen dentro de E , combinadas de la siguiente manera:
- Si E es una expresión de identificador , la expresión E es su único resultado potencial.
- Si E es una expresión de subíndice ( E1 [ E2 ] ) donde uno de los operandos es un array, los resultados potenciales de ese operando se incluyen en el conjunto.
- Si E es una expresión de acceso a miembro de clase de la forma E1. E2 o E1. template E2 que nombra un miembro de datos no estático, los resultados potenciales de E1 se incluyen en el conjunto.
- Si E es una expresión de acceso a miembro de clase que nombra un miembro de datos estático, la expresión de identificador que designa el miembro de datos se incluye en el conjunto.
- Si E es una expresión de acceso a miembro de puntero de la forma E1. * E2 o E1. * template E2 cuyo segundo operando es una expresión constante, los resultados potenciales de E1 se incluyen en el conjunto.
- Si E es una expresión entre paréntesis ( ( E1 ) ), los resultados potenciales de E1 se incluyen en el conjunto.
- Si E es una expresión condicional glvalue ( E1 ? E2 : E3 , donde E2 y E3 son glvalues), la unión de los resultados potenciales de E2 y E3 se incluyen ambos en el conjunto.
- Si E es una expresión coma ( E1, E2 ), los resultados potenciales de E2 están en el conjunto de resultados potenciales.
- En caso contrario, el conjunto está vacío.
Uso de ODR (definición informal)
Un objeto es odr-usado si su valor es leído (a menos que sea una constante en tiempo de compilación) o escrito, se toma su dirección, o se le vincula una referencia.
Una referencia es odr-usada si se utiliza y su referente no se conoce en tiempo de compilación,
Una función es odr-usada si se realiza una llamada a función a ella o se toma su dirección.
Si una entidad es odr-utilizada, su definición debe existir en algún lugar del programa; una violación de esto es usualmente un error de enlazado.
struct S { static const int x = 0; // miembro de datos estático // se requiere 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 se usa odr aquí : f(S::x); // S::x se usa odr aquí: se requiere una definición
Uso de ODR (definición formal)
Una variable
x
que es nombrada por una
expresión potencialmente evaluada
expr
que aparece en un punto
P
es odr-usada por
expr
, a menos que se cumpla alguna de las siguientes condiciones:
-
x
es una referencia que es
utilizable en expresiones constantes
en
P. -
x
no es una referencia y
(hasta C++26)
expr
es un elemento del conjunto de resultados potenciales de una expresión
E
, y se satisface alguna de las siguientes condiciones:
- E es una expresión de valor descartado , y no se le aplica conversión de lvalue-a-rvalue.
-
x
es un objeto
no volátil
(desde C++26)
que es utilizable en expresiones constantes en
Py no tiene subobjetos mutables, y se satisface alguna de las siguientes condiciones:
|
(desde C++26) |
-
-
- E tiene un tipo no calificado como volátil y no es de clase, y se le aplica la conversión de lvalue a rvalue.
-
struct S { static const int x = 1; }; // aplicar conversión lvalue-a-rvalue // a S::x produce una expresión constante int f() { S::x; // expresión de valor descartado no utiliza odr S::x return S::x; // expresión donde se aplica conversión lvalue-a-rvalue // no utiliza odr S::x }
* this se usa odr si this aparece como una expresión potencialmente evaluada (incluyendo el this implícito en una expresión de llamada a función miembro no estática).
|
Un structured binding se considera odr-used si aparece como una expresión potencialmente evaluada. |
(since C++17) |
Una función es odr-usada en los siguientes casos:
- Una función es odr-usada si es nombrada por (ver abajo) una expresión o conversión potencialmente evaluada.
- Una función miembro virtual es odr-usada si no es una función miembro virtual pura (se requieren las direcciones de las funciones miembro virtual para construir la vtable).
- Una función de asignación o desasignación no de colocación para una clase es odr-usada por la definición de un constructor de esa clase.
- Una función de desasignación no de colocación para una clase es odr-usada por la definición del destructor de esa clase, o al ser seleccionada por la búsqueda en el punto de definición de un destructor virtual.
-
Un operador de asignación en una clase
Tque es miembro o base de otra claseUes odr-usado por las funciones de asignación de copia o asignación de movimiento implícitamente definidas deU. - Un constructor (incluyendo constructores por defecto) para una clase es odr-usado por la inicialización que lo selecciona.
- Un destructor para una clase es odr-usado si es potencialmente invocado .
|
Esta sección está incompleta
Motivo: lista de todas las situaciones donde el uso odr marca una diferencia |
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 261 | C++98 |
una función de desasignación para una clase polimórfica
podría ser odr-usada incluso si no hubiera expresiones new o delete relevantes en el programa |
se complementaron los
casos de odr-use para cubrir constructores y destructores |
| CWG 678 | C++98 |
una entidad podría tener definiciones
con diferentes vinculaciones de lenguaje |
el comportamiento es
indefinido en este caso |
| CWG 1472 | C++98 |
variables de referencia que cumplen los requisitos para
aparecer en una expresión constante eran odr-usadas incluso si la conversión lvalue-a-rvalue se aplicaba inmediatamente |
no son
odr-usadas en este caso |
| CWG 1614 | C++98 | tomar la dirección de una función virtual pura la odr-usaba | la función no es odr-usada |
| CWG 1741 | C++98 |
objetos constantes que son inmediatamente convertidos
lvalue-a-rvalue en expresiones potencialmente evaluadas eran odr-usados |
no son odr-usados |
| CWG 1926 | C++98 | las expresiones de subíndice de array no propagaban resultados potenciales | propagan |
| CWG 2242 | C++98 |
no estaba claro si un objeto
const
que solo está
constantemente inicializado en parte de sus definiciones viola ODR |
ODR no se viola; el objeto está
constantemente inicializado en este caso |
| CWG 2300 | C++11 |
las expresiones lambda en diferentes unidades
de traducción nunca podían tener el mismo tipo de clausura |
el tipo de clausura puede ser el
mismo bajo la regla de una definición |
| CWG 2353 | C++98 |
un miembro de datos estático no era un resultado potencial
de una expresión de acceso a miembro que lo accedía |
lo es |
| CWG 2433 | C++14 |
una plantilla de variable no podía tener
múltiples definiciones en un programa |
puede |
Referencias
- Estándar C++23 (ISO/IEC 14882:2024):
-
- 6.3 Regla de una definición [basic.def.odr]
- Estándar C++20 (ISO/IEC 14882:2020):
-
- 6.3 Regla de una definición [basic.def.odr]
- Estándar C++17 (ISO/IEC 14882:2017):
-
- 6.2 Regla de una definición [basic.def.odr]
- Estándar C++14 (ISO/IEC 14882:2014):
-
- 3.2 Regla de una definición [basic.def.odr]
- Estándar C++11 (ISO/IEC 14882:2011):
-
- 3.2 Regla de una definición [basic.def.odr]
- Estándar C++03 (ISO/IEC 14882:2003):
-
- 3.2 Regla de una definición [basic.def.odr]
- Estándar C++98 (ISO/IEC 14882:1998):
-
- 3.2 Regla de una definición [basic.def.odr]