c++ - resueltos - ¿Por qué la definición de una clase en archivos cpp no causa un error de enlazador?
programacion orientada a objetos ejemplos resueltos (4)
Cada archivo cpp compila todas las definiciones de forma independiente. No hay diferencia desde la perspectiva del compilador de definirlo dos veces en dos archivos cpp diferentes, o una vez en una inclusión compartida.
Una definición de clase en sí misma no introduce ningún símbolo definido por el usuario, y por lo tanto no producirá un error de enlazador. Solo los métodos de clase y los miembros estáticos lo hacen. Si los métodos están definidos dentro de la definición de clase, se tratan como en línea. Las funciones en línea están marcadas de tal manera que el vinculador seleccionará cualquier definición disponible y supondrá que es equivalente a todas las demás. Si estos métodos no están definidos dentro de la definición de la clase y no están marcados en línea, varias instancias darán como resultado un error del enlazador.
Si tengo un archivo foo.cpp
con el siguiente código:
class Foo {
};
class Foo {
};
int main() {
return 0;
}
Entonces, naturalmente, me da error: redefinition of ''Foo''
. Sin embargo, si tengo foo.cpp
con
class Foo {
};
int main() {
return 0;
}
Y bar.cpp
con
class Foo {
};
A pesar de que la class Foo
se definió dos veces en el programa, todo esto compila bien.
Si hubiera puesto int something;
en ambos archivos en el espacio de nombres global, entonces habría obtenido un error de enlazador (específicamente duplicate symbol
), pero para las definiciones de clase, esto nunca sucede.
Sé declaraciones de funciones como int doIt();
se puede duplicar en ambos archivos cpp, pero una definición , por ejemplo int doIt() {}
no puede ser. Ahora, en el primer error del compilador (con la class Foo{};
dos veces en un archivo cpp), decía la redefinition of foo
, por lo que la class Foo{};
es una definición. Entonces, ¿por qué, a diferencia de las funciones, puede definirse dos veces en un programa?
EDITAR: según este sitio web , las clases nombradas tienen enlaces externos. Entonces, ¿por qué entonces no hay choque entre la class Foo
en ambos archivos cpp?
EDIT2: según el sitio web vinculado anteriormente, no solo las clases con nombre tienen enlaces externos, sino también sus miembros estáticos. Sin embargo, todo esto compila bien:
foo.cpp
:
class Foo {
public:
int foo();
static int x;
};
int Foo::foo() {
return 5;
}
int main() {
return 0;
}
bar.cpp
:
class Foo {
public:
int foo(int);
static bool x;
};
int Foo::foo(int i) {
return i * 2;
}
No solo Foo::foo
ha sido redefinido con una firma diferente, sino que Foo::x
es de un tipo diferente. Ambos deberían tener enlaces externos, pero este código es A-ok.
Normalmente, las clases se definen en archivos de encabezado y, si lo hace, obtendrá errores al incluir el archivo de encabezado.
Con respecto a su primera pregunta, con definiciones idénticas en múltiples TU, eso está explícitamente permitido por ODR, porque de lo contrario el lenguaje sería inútil.
En cuanto a la segunda pregunta, con diferentes definiciones en diferentes TU, es una violación de ODR. Sin embargo, esos son NDR. Su programa aún está mal formado y probablemente cause errores extraños.
En cuanto a la tercera pregunta, con static
miembros de datos static
, esas son declaraciones, no definiciones. Necesitan una definición única, como:
TheType ClassName::VariableName;
Esos normalmente se colocan en el archivo .cpp que lo acompaña.
Hay una excepción a eso, con static
miembros de datos static
con los inicializadores en línea.
ODR = Una regla de definición
TU = Unidad de traducción
NDR = No se requiere diagnóstico
Una nota con respecto a NDR; algunos tipos de errores son difíciles de detectar para el compilador, y el estándar generalmente no requiere que el compilador emita un diagnóstico (es decir, advertencia o error) en esos casos. Hay herramientas, como CppLint, que pueden detectar muchos de los errores que el compilador no puede. Cuando se trata de violaciones de ODR, generalmente se pueden evitar definiendo solo tipos en los encabezados.
Debido a " Una regla de definición " en C ++. No puede redefinir la clase dentro de una unidad de traducción, pero la clase puede (y debe) definirse en cada unidad de traducción que la utiliza. Es por eso que los encabezados y #include existen en C / C ++. Debe colocar la definición de clase en el encabezado e incluirla en cada .cpp que lo use. Previene la violación de ODR pero técnicamente el uso de #include es lo mismo que la definición de la clase en cada archivo .cpp (el preprocesador solo hace que el archivo incluido sea parte del archivo compilado).
También preste atención a cómo la definition
difiere de la declaration
en C ++.
Upd. En su nuevo ejemplo con variables miembro estáticas solo tiene declaraciones sin definición :
class Foo {
public:
static int x; // <-- variable declaration
};
int Foo::x; // <-- variable definition
Uno puede duplicar las declaraciones dentro de la unidad de traducción, pero no las definiciones.
La definición de tipos (incluidas las clases) se puede duplicar en diferentes unidades de traducción, funciones y variables con enlace externo, no.
La definición de dos tipos en dos unidades de traducción con el mismo nombre pero diferente estructura es la violación de ODR que los enlazadores generalmente no pueden diagnosticar: su programa es incorrecto, pero todo "se desarrolla correctamente".
La unidad de traducción es lo que el compilador obtiene como entrada después del preprocesamiento. Usando clang o gcc puedes obtenerlo así:
$ clang -E foo.cpp >foo.ii