La regla de definición única ( ODR , por sus siglas en inglés ) es una regla importante del lenguaje de programación C++ que establece que las clases/estructuras y las funciones no inline no pueden tener más de una definición en todo el programa, y las plantillas y los tipos no pueden tener más de una definición por unidad de traducción . Está definida en el estándar ISO C++ ( ISO/IEC 14882 ) de 2003, en la sección 3.2. Otros lenguajes de programación tienen reglas similares, pero definidas de manera diferente, para lograr el mismo objetivo.
Resumen
En resumen, el ODR establece 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 inline no puede tener más de una definición; si se utiliza un objeto o función, debe tener exactamente una definición. Se puede declarar un objeto o función que nunca se utilice, en cuyo caso no es necesario proporcionar una definición. En ningún caso puede haber más de una definición.
- Algunos elementos, como los tipos, las plantillas y las funciones externas en línea, pueden definirse en más de una unidad de traducción. Para una entidad dada, cada definición debe tener la misma secuencia de tokens . Los objetos y funciones no externos en diferentes unidades de traducción son entidades distintas, aunque sus nombres y tipos sean iguales.
Algunas violaciones de la ODR deben ser diagnosticadas por el compilador . Otras violaciones, en particular aquellas que abarcan unidades de traducción, no requieren ser diagnosticadas. [ 1 ]
Ejemplos
MyClassEn general, una unidad de traducción no debe contener más de una definición de ningún tipo de clase. En este ejemplo, aparecen dos definiciones del tipo de clase en la misma unidad de traducción . Esto suele ocurrir si un archivo de cabecera se incluye dos veces en el mismo archivo fuente sin las protecciones de cabecera adecuadas .
clase MyClass {}; // primera definición de MyClass clase MyClass {}; // error, segunda definición de MyClassA continuación, formar un puntero MyStructo definir una función que tome una referencia MyStructson ejemplos de construcciones válidas, ya que no requieren que el tipo MyStructsea completo . Por lo tanto, no se requiere una definición. [ 2 ]
Definir un objeto de tipo MyStruct, una función que toma un argumento de tipo MyStruct, o usar MyStructen una expresión sizeof son ejemplos de contextos donde S debe ser completo y, por lo tanto, requieren una definición. [ 2 ]
struct MyStruct ; // declaración de MyStruct MyStruct * p ; // ok, no se requiere definición void f ( MyStruct & ); // ok, no se requiere definición void f ( MyStruct * ); // ok, no se requiere definición MyStruct f (); // ok, no se requiere definición: ¡esta es solo una declaración de función!MyStruct s ; // error, se requiere definición sizeof ( MyStruct ); // error, se requiere definiciónMás de una definición
En ciertos casos, puede haber más de una definición de un tipo o una plantilla. Un programa que consta de varios archivos de cabecera y archivos fuente 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 estáticos constantes
En C++ preestá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 enteros estáticos constantes. La intención era permitir usos como:
struct MyClass { static const int N = 10 ; }; char data [ MyClass :: N ]; // N "usado" sin definición fuera de la clasesin una definición de ámbito de espacio de nombres para N.
Sin embargo, la redacción del estándar C++ de 1998 aún 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 en la práctica hacía que lo anterior fuera incorrecto. [ 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 necesidad de una definición fuera de la clase. Esto incluye límites de matrices , expresiones de casos , inicializadores de miembros estáticos y argumentos de plantillas que no son de tipo . [ 6 ]
struct MyClass1 { static const int N = 10 ; static const int U = N ; // Válido según C++03 };char data [ Class1 :: N ]; // Válido según C++03plantilla < int > struct MyClass2 ;plantilla <> struct MyClass2 < Class1 :: N > {}; // Válido según C++03Sin embargo, el uso de un miembro entero constante estático en cualquier lugar que no sea donde se requiere una expresión constante integral, requiere una definición: [ 7 ]
struct MyClass { static const int N = 10 ; };int main () { int i = MyClass :: N ; // Mal formado en C++03. Se requiere la definición de MyClass::N. }Este requisito se relajó en un estándar posterior, C++11 . [ 7 ]
Ejemplo que muestra efectos secundarios inesperados
Necesitamos 4 archivos: " Base.cppm ", " Dummy1.cppm ", " Dummy2.cppm ", " Main.cpp ".
Base.cppm :
módulo ;exportar módulo wikipedia . ejemplos . Base ;exportar espacio de nombres wikipedia :: ejemplos {// clase base abstracta class Base { public : virtual void myFunc ( ) = 0 ; virtual ~ Base () = default ; };}Dummy1.cppm
módulo ;exportar módulo wikipedia . ejemplos . Dummy2 ;importar std ;import wikipedia . examples . Base ;exportar espacio de nombres wikipedia :: ejemplos {clase Dummy : public Base { public : void myFunc () override { std :: println ( "odr ONE dummy: Hola" ); } };Base * odr1Create () { return new Dummy (); }}Dummy2.cppm :
módulo ;exportar módulo wikipedia . ejemplos . Dummy2 ;importar std ;import wikipedia . examples . Base ;exportar wikipedia :: ejemplos {clase Dummy : public Base { public : void myFunc () override { std :: println ( "odr TWO dummy: World" ); } };Base * odr2Create () { return new Dummy (); }}Main.cpp :
import wikipedia.examples.Base ; import wikipedia.examples.Dummy1 ; import wikipedia.examples.Dummy2 ;using wikipedia :: examples :: Base ; using wikipedia :: examples :: Dummy1 ; using wikipedia :: examples :: Dummy2 ;int main ( int argc , char * argv []) { Base * o1 = odr1Create (); Base * o2 = odr2Create (); o1 -> myFunc (); o2 -> myFunc ();eliminar o1 ; eliminar o2 ; }Al ejecutarse, el resultado esperado es:
odr UN maniquí: Hola odr DOS ficticio: Mundo
Pero el resultado probable es:
odr UN maniquí: Hola odr UN 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 Dummyclases diferentes), y eso solo funciona si los nombres de las clases son diferentes.
Véase también
Referencias
- ↑ ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §3.2 Regla de una definición [basic.def.odr] párr. 3
- 1 2 ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §3.2 Regla de una definición [basic.def.odr] párr. 4
- ↑ ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §3.2 Regla de una 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 Regla de una 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
- 1 2 "¿Cuándo se requiere una definición de un miembro de datos estático?" . WG21 . Consultado el 15 de abril de 2009 .
- C++