error de definición múltiple que incluye el archivo de encabezado c ++ con código en línea de múltiples fonts

Tengo un archivo de encabezado c ++ que contiene una clase. Quiero usar esta clase en varios proyectos, pero no quiero crear una biblioteca separada para él, así que estoy poniendo declaraciones de métodos y definiciones en el archivo de encabezado:

// example.h #ifndef EXAMPLE_H_ #define EXAMPLE_H_ namespace test_ns{ class TestClass{ public: void testMethod(); }; void TestClass::testMethod(){ // some code here... } } // end namespace test_ns #endif 

Si dentro del mismo proyecto multiple definition of test_ns::TestClass::testMethod() este encabezado de más de un archivo cpp, multiple definition of test_ns::TestClass::testMethod() un error que dice ” multiple definition of test_ns::TestClass::testMethod() “, mientras que si pongo la definición del método dentro del cuerpo de la clase esto no ocurrir:

 // example.h #ifndef EXAMPLE_H_ #define EXAMPLE_H_ namespace test_ns{ class TestClass{ public: void testMethod(){ // some code here... } }; } // end namespace test_ns #endif 

Dado que la clase se define dentro de un espacio de nombres, ¿no deberían las dos formas ser equivalentes? ¿Por qué se considera que el método se definió dos veces en el primer caso?

Estos no son equivalentes. El segundo ejemplo dado tiene un modificador “en línea” implícito en el método y, por lo tanto, el comstackdor reconciliará varias definiciones (lo más probable es que tenga un enlace interno del método si no es inlineable).

El primer ejemplo no está en línea y, por lo tanto, si este encabezado se incluye en varias unidades de traducción, tendrá múltiples definiciones y errores de enlazador.

Además, los encabezados siempre deben estar protegidos para evitar múltiples errores de definición en la misma unidad de traducción. Eso debería convertir tu encabezado en:

 #ifndef EXAMPLE_H #define EXAMPLE_H //define your class here #endif 

Dentro del cuerpo de la clase se considera que está en línea por el comstackdor. Si implementa fuera del cuerpo, pero aún en el encabezado, debe marcar el método como ‘en línea’ explícitamente.

 namespace test_ns{ class TestClass{ public: inline void testMethod(); }; void TestClass::testMethod(){ // some code here... } } // end namespace test_ns 

Editar

Para mí, a menudo ayuda a resolver este tipo de problemas de comstackción al darme cuenta de que el comstackdor no ve nada como un archivo de encabezado. Los archivos de encabezado se preprocesan y el comstackdor solo ve un archivo enorme que contiene cada línea de cada archivo (recursivamente) incluido. Normalmente, el punto de partida para estos recursivos incluye es un archivo fuente cpp que se está comstackndo. En nuestra compañía, incluso un archivo cpp de apariencia modesta se puede presentar al comstackdor como un monstruo de 300000 líneas.

Entonces, cuando un método, que no está declarado en línea, se implementa en un archivo de cabecera, el comstackdor podría terminar viendo vacío TestClass :: testMethod () {…} docenas de veces en el archivo preprocesado. Ahora puede ver que esto no tiene sentido, el mismo efecto que obtendría al copiarlo / pegarlo varias veces en un archivo fuente. E incluso si tuvo éxito solo al tenerlo una vez en cada unidad de comstackción, mediante alguna forma de comstackción condicional (por ejemplo, usando corchetes de inclusión), el enlazador aún encontraría el símbolo de este método en múltiples unidades comstackdas (archivos de objeto).

No coloque una definición de función / método en un archivo de encabezado a menos que estén en línea (definiéndolos directamente en una statement de clase o explícitamente especificado por la palabra clave inline)

los archivos de encabezado son (principalmente) para la statement (lo que necesite declarar). Las definiciones permitidas son las de constantes y funciones / métodos en línea (y también plantillas).

En realidad, es posible tener definiciones en un único archivo de encabezado (sin un archivo .c / .cpp separado) y aún así poder usarlo desde múltiples archivos fuente.

Considere este encabezado de foobar.h :

 #ifndef FOOBAR_H #define FOOBAR_H /* write declarations normally */ void foo(); void bar(); /* use conditional comstacktion to disable definitions when necessary */ #ifndef ONLY_DECLARATIONS void foo() { /* your code goes here */ } void bar() { /* your code goes here */ } #endif /* ONLY_DECLARATIONS */ #endif /* FOOBAR_H */ 

Si usa este encabezado en un solo archivo fuente, inclúyalo y úselo normalmente. Como en main.c :

 #include "foobar.h" int main(int argc, char *argv[]) { foo(); } 

Si hay otros archivos fuente en su proyecto que requieren foobar.h , entonces #define ONLY_DECLARATIONS macro #define ONLY_DECLARATIONS antes de incluirla. En use_bar.c puedes escribir:

 #define ONLY_DECLARATIONS #include "foobar.h" void use_bar() { bar(); } 

Después de la comstackción, use_bar.o y main.o se pueden vincular entre sí sin errores, porque solo uno de ellos (main.o) tendrá implementación de foo () y bar ().

Eso es poco idiomático, pero permite mantener definiciones y declaraciones juntas en un archivo. Siento que es un sustituto de los pobres para los módulos verdaderos.

Su primer fragmento de código no cumple con la “Regla de una sola definición” de C ++ . Consulte aquí un enlace a un artículo de Wikipedia que describe ODR. De hecho, no está del punto 2 porque cada vez que el comstackdor incluye el archivo de encabezado en un archivo fuente, se corre el riesgo de que el comstackdor genere una definición globalmente visible de test_ns::TestClass::testMethod() . Y, por supuesto, cuando logre vincular el código, el vinculador tendrá gatitos, ya que encontrará el mismo símbolo en varios archivos de objetos.

El segundo fragmento funciona porque ha introducido la definición de la función, lo que significa que incluso si el comstackdor no genera ningún código en línea para la función (por ejemplo, tiene desactivada la alineación o el comstackdor decide que la función también es grande a en línea), el código generado para la definición de la función será visible solo en la unidad de traducción, como si lo hubiera pegado en un espacio de nombre anónimo. Por lo tanto, obtiene múltiples copias de la función en el código objeto generado que el vinculador puede optimizar o no según lo inteligente que sea.

Puede lograr un efecto similar en su primer fragmento de código al TestClass::testMethod() prefijo TestClass::testMethod() con inline .

 //Baseclass.h or .cpp #ifndef CDerivedclass #include "Derivedclass.h" #endif or //COthercls.h or .cpp #ifndef CCommonheadercls #include "Commonheadercls.h" #endif I think this suffice all instances.