2008-10-17 21 views
25

Tengo un archivo de encabezado C++ que contiene una clase. Quiero usar esta clase en varios proyectos, bu no quiero crear una biblioteca independiente para él, así que estoy poniendo ambas declaraciones métodos y definiciones en el archivo de cabecera:error de definición múltiple incluyendo archivo de encabezado C++ con código en línea de varias fuentes

// 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 de la mismo proyecto incluyo esta cabecera de más de un archivo CPP, me sale 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 sucede:

// 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 se define la clase 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?

Respuesta

20

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 compilador 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, por lo que 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 protegerse para evitar múltiples errores de definición en la misma unidad de traducción. Eso debería convertir su cabecera a:

#ifndef EXAMPLE_H 
#define EXAMPLE_H 

//define your class here 

#endif 
+0

Gracias por señalar esto ... Había olvidado los guardias de inclusión en el ejemplo (pero no en el código real). –

20

Dentro del cuerpo de la clase se considera que está en línea por el compilador. 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

En cuanto a mí, a menudo ayuda a resolver este tipo de problemas de compilación al darse cuenta de que el compilador no se ve nada como un archivo de cabecera. Los archivos de encabezado se preprocesan y el compilador 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á compilando. En nuestra compañía, incluso un archivo cpp de apariencia modesta se puede presentar al compilador 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 compilador 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 compilación, mediante algún tipo de compilación condicional (por ejemplo, usando corchetes de inclusión) el enlazador aún encontraría el símbolo de este método en varias unidades compiladas (archivos de objeto).

3

No ponga una definición de función/método en un archivo de cabecera a menos que estén entre líneas (definiendo directamente en una declaración de clase o explícitamente especificado por la palabra clave en línea)

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

1

Su primer fragmento de código está fallando con la "Regla de una sola definición" de C++ - see here for a link to a Wikipedia article describing ODR. En realidad, está en desacuerdo con el punto 2 porque cada vez que el compilador incluye el archivo de encabezado en un archivo fuente, corre el riesgo de compilador que genera una definición global 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 compilador no genera ningún código en línea para la función (por ejemplo, ha desactivado la alineación o el compilador decide la función es demasiado grande para alinear), 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 anteponer TestClass::testMethod() con inline.

-1
//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. 
2

En realidad, es posible tener las definiciones en un archivo de cabecera única (sin un archivo separado .c/Cpp) y aún así ser capaz de utilizarlo desde múltiples archivos de origen.

consideran este foobar.h cabecera:

#ifndef FOOBAR_H 
#define FOOBAR_H 

/* write declarations normally */ 
void foo(); 
void bar(); 

/* use conditional compilation 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 utiliza esta cabecera en un solo archivo de origen, y utilizaran normalmente. Al igual que en main.c:

#include "foobar.h" 

int main(int argc, char *argv[]) { 
    foo(); 
} 

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

#define ONLY_DECLARATIONS 
#include "foobar.h" 

void use_bar() { 
    bar(); 
} 

Después use_bar.o compilación y main.o pueden ser unidos entre sí sin errores, porque sólo uno de ellos (main.o) tendrá aplicación de foo() y bar ()

Eso no es un poco idiomático, pero permite mantener definiciones y declaraciones juntas en un archivo. Siento que es un sustituto de un hombre pobre para el verdadero modules.

Cuestiones relacionadas