La regla de una definición ( ODR ) es una regla importante del lenguaje de programación C ++ que prescribe que los objetos y las funciones no en línea no pueden tener más de una definición en todo el programa y la plantilla y los tipos no pueden tener más de una definición por unidad de traducción . Se define en la norma ISO C ++ ( ISO / IEC 14882 ) 2003, en la sección 3.2.
Resumen
En resumen, la ODR afirma que:
- En cualquier unidad de traducción, una plantilla , tipo , función u objeto no puede tener más de una definición. Algunos de estos pueden tener cualquier número de declaraciones. Una definición proporciona una instancia.
- En todo el programa , un objeto o función no en línea no puede tener más de una definición; si se utiliza un objeto o función, debe tener exactamente una definición. Puede declarar un objeto o función que nunca se utiliza, en cuyo caso no es necesario que proporcione una definición. En ningún caso puede haber más de una definición.
- Algunas cosas, como tipos, plantillas y funciones en línea externas , se pueden definir en más de una unidad de traducción. Para una entidad determinada, cada definición debe tener la misma secuencia de tokens . Los objetos y funciones no externos en diferentes unidades de traducción son entidades diferentes, incluso si sus nombres y tipos son los mismos.
El compilador debe diagnosticar algunas infracciones de la ODR . No es necesario diagnosticar otras infracciones, en particular las que abarcan unidades de traducción. [1]
Ejemplos de
En general, una unidad de traducción no debe contener más de una definición de cualquier tipo de clase. En este ejemplo, dos definiciones del tipo de clase C ocurren en la misma unidad de traducción . Esto suele ocurrir si un archivo de encabezado se incluye dos veces en el mismo archivo de origen sin los protectores de encabezado adecuados .
clase C {}; // primera definición de C class C {}; // error, segunda definición de C
A continuación, formar un puntero a S o definir una función tomando una referencia a S son ejemplos de construcciones legales, porque no requieren que el tipo de S esté completo . Por lo tanto, no se requiere una definición. [2]
Definir un objeto de tipo S, una función que toma un argumento de tipo S o usar S en una expresión de tamaño de son ejemplos de contextos donde S debe ser completo y, por lo tanto, requieren una definición. [2]
estructura S ; // declaración de S S * p ; // ok, no se requiere definición void f ( S & ); // ok, no se requiere definición void f ( S * ); // ok, no se requiere definición S f (); // ok, no se requiere definición - ¡esta es solo una declaración de función!S s ; // error, definición requerida sizeof ( S ); // error, se requiere definición
Más de una definición
En ciertos casos, puede haber más de una definición de un tipo o plantilla. Un programa que consta de varios archivos de encabezado y archivos de origen normalmente tendrá más de una definición de un tipo, pero no más de una definición por unidad de traducción.
Si un programa contiene más de una definición de un tipo, entonces cada definición debe ser equivalente. [3]
Definiciones de miembros de datos constantes estáticos
En C ++ anterior al estándar, todos los miembros de datos estáticos requerían una definición fuera de su clase. Sin embargo, durante el proceso de estandarización de C ++, se decidió eliminar este requisito para los miembros integrales const estáticos. La intención era permitir usos como:
struct C { static const int N = 10 ; }; char datos [ C :: N ]; // N "usado" sin una definición fuera de clase
sin una definición de ámbito de espacio de nombres para N
.
Sin embargo, la redacción del estándar C ++ de 1998 todavía requería una definición si el miembro se usaba en el programa. [4] Esto incluía que el miembro apareciera en cualquier lugar excepto como operando de sizeof o typeid , lo que efectivamente hacía que lo anterior estuviera mal formado. [5]
Esto se identificó como un defecto, y la redacción se ajustó para permitir que dicho miembro aparezca en cualquier lugar donde se requiera una expresión constante , sin requerir una definición fuera de clase. Esto incluye límites de matriz , expresiones de casos , inicializadores de miembros estáticos y argumentos de plantilla sin tipo . [6]
struct C { static const int N = 10 ; estática constante int U = N ; // Legal según C ++ 03 };char datos [ C :: N ]; // Legal según C ++ 03plantilla < int > estructura D ;plantilla <> estructura D < C :: N > {}; // Legal según C ++ 03
Sin embargo, el uso de un miembro integral constante estático en cualquier lugar, excepto donde se requiere una expresión constante integral, requiere una definición: [7]
struct C { static const int N = 10 ; };int main () { int i = C :: N ; // Mal formado en C ++ 03. Se requiere una definición de C :: N. }
Este requisito se relajó en un estándar posterior, C ++ 11 . [7]
Ejemplo que muestra efectos secundarios inesperados
Necesitamos 4 archivos: "odr.h", "main.cpp", "odr1.cpp", "odr2.cpp"
El acrónimo "odr" aquí es la abreviatura de "Regla de una definición".
odr.h:
// clase base abstracta clase CBase { public : virtual void xxx () = 0 ; virtual ~ CBase () = predeterminado ; };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 #include "odr.h"class CDummy : public CBase { public : void xxx () override { printf ( "odr ONE dummy: Hola \ n " ); } };CBase * odr1_create () { devolver nuevo CDummy (); }
odr2.cpp
#include #include "odr.h"clase CDummy : public CBase { public : void xxx () override { printf ( "odr DOS dummy: World \ n " ); } };CBase * odr2_create () { devolver nuevo CDummy (); }
En un shell de Linux para probar, compile con:
g ++ -c odr1.cppg ++ -c odr2.cppg ++ -c main.cppg ++ -o odr main.o odr1.o odr2.o
En un "Símbolo del sistema de herramientas de compilación" de Windows Visual Studio, compile con:
cl / c main.cppcl / c odr1.cppcl / c odr2.cppcl /Feodr.exe main.obj odr1.obj odr2.obj
Cuando se ejecuta, la salida esperada es:
odr ONE maniquí: Holaodr DOS maniquí: Mundo
Pero es muy probable que obtenga:
odr ONE maniquí: Holaodr ONE maniquí: Hola
El problema es que el enlazador de C ++ tiene que averiguar cómo construir la tabla de métodos virtuales para las (dos diferentes) clases "CDummy", y eso solo funciona si los nombres de las clases son diferentes.
Ver también
Referencias
- ^ ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §3.2 Una regla de definición [basic.def.odr] párr. 3
- ^ a b ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §3.2 Una regla de definición [basic.def.odr] párr. 4
- ^ ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §3.2 Una regla de definición [basic.def.odr] párr. 5
- ^ ISO / IEC (1998). ISO / IEC 14882: 1998 (E): Lenguajes de programación - C ++ §9.4.2 Miembros de datos estáticos [class.static.data] párr. 4
- ^ ISO / IEC (1998). ISO / IEC 14882: 1998 (E): Lenguajes de programación - C ++ §3.2 Una regla de definición [basic.def.odr] párr. 2
- ^ ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §5.19 Expresiones constantes [expr.const] párr. 1
- ^ a b "¿Cuándo se requiere una definición de miembro de datos estáticos?" . WG21 . Consultado el 15 de abril de 2009 .