One Definition Rule (ODR) es una regla fundamental de C++ que requiere que dentro de todo el programa (todas las unidades de traducción) cada objeto, función o clase tenga exactamente una implementación definida.
La ODR se viola si:
Esto lleva a errores difíciles de rastrear, comportamientos impredecibles, enlace incorrecto o, lo que es peor, comportamiento diferente del programa dependiendo de cómo se haya vinculado.
Por qué se viola:
En grandes proyectos, a menudo se copian/modifican archivos .h sin control de versiones o se separa el código en múltiples módulos con compilación separada. Si alguien cambia una función inline solo en un lugar, los demás archivos fuente pueden tener una versión antigua.
Cómo evitarlo:
constexpr o, con C++17, variables inline.¿Se pueden definir funciones estáticas (static void foo()) con el mismo nombre y diferentes implementaciones en diferentes archivos .cpp sin consecuencias?
Muchos creen que las funciones estáticas no se afectan entre módulos. La respuesta es: sí, se puede, porque cada una tiene un enlace interno (solo es visible dentro de su unidad de traducción). Sin embargo, esto no se garantiza para funciones inline y plantillas, lo que a menudo causa confusión.
Ejemplo:
// file1.cpp static void foo() { std::cout << "A"; } // file2.cpp static void foo() { std::cout << "B"; }
Las llamadas en estos módulos serán independientes.
Historia
En un gran proyecto, un desarrollador cambió el cuerpo de una función inline solo en uno de los clones del archivo .h. Después de la compilación, cierto comportamiento se volvió impredecible: parte de los módulos trabajaban con el antiguo algoritmo, parte con el nuevo. La razón fue la violación de la ODR para la función inline.
Historia
Al migrar a C++17, una variable constante se definía en varios archivos de encabezado sin usar la palabra clave inline. Aparecieron múltiples errores de símbolo duplicado en el enlace. Se corrigió declarando la variable como
inline const.
Historia
Se descubrió tarde que el archivo .h generado por el sistema de construcción contenía dos implementaciones del mismo método de una clase plantilla, diferenciándose por una comprobación ifdef en diferentes compilaciones. Más tarde, la aplicación se bloqueaba periódicamente debido a discrepancias en ABI entre los módulos vinculados.