2009-11-27 15 views
28

Me preguntaba cuáles son las diferencias entre declarar e implementar una clase únicamente en un archivo de encabezado, en comparación con el enfoque normal en el que protype clase en el encabezado e implementar en un archivo .cpp efectivo.Diferencia entre implementar una clase dentro de un archivo .h o en un archivo .cpp

Para explicar mejor lo que estoy hablando me refiero a las diferencias entre aproximación normal:

// File class.h 
class MyClass 
{ 
private: 
    //attributes 
    public: 
    void method1(...); 
    void method2(...); 
    ... 
}; 

//file class.cpp 
#include "class.h" 

void MyClass::method1(...) 
{ 
    //implementation 
} 

void MyClass::method2(...) 
{ 
    //implementation 
} 

y una justo cabecera enfoque:

// File class.h 
class MyClass 
{ 
private: 
    //attributes 
public: 
    void method1(...) 
    { 
     //implementation 
    } 

    void method2(...) 
    { 
    //implementation 
    } 

    ... 
}; 

yo puede conseguir la diferencia principal: en el segundo caso, el código se incluye en todos los demás archivos que lo necesitan generando más instancias de las mismas implementaciones, por lo que una redundancia implícita; mientras que en el primer caso, el código se compila solo y luego cada llamada referida al objeto MyClass está vinculada a la implementación en class.cpp.

¿Pero hay otras diferencias? ¿Es más conveniente usar un enfoque en lugar de otro dependiendo de la situación? También he leído en alguna parte que la definición del cuerpo de un método directamente en un archivo de encabezado es una solicitud implícita al compilador para alinear ese método, ¿es cierto?

+0

Si tiene las protecciones de preprocesador adecuadas, no creará más instancias de la misma implementación. –

+0

¿Estás hablando de #ifndef _CLASS_H_ #define _CLASS_H_ etc. ..? – Jack

+9

@Andrew: claro que sí, si son dos.Los archivos cpp incluyen el mismo encabezado, luego cada compilará su contenido completo independientemente de las protecciones del preprocesador, y ese contenido compilado estará presente en el archivo del objeto de salida. Los duplicados solo serán eliminados por el vinculador. Los protectores del preprocesador deben manejar con gracia la doble inclusión de un encabezado por una sola unidad de traducción, sin compartir entre las unidades de traducción. –

Respuesta

33

La principal diferencia práctica es que si las definiciones de funciones miembro están en el cuerpo del encabezado, entonces, por supuesto, se compilan una vez para cada unidad de traducción que incluye ese encabezado. Cuando su proyecto contiene unos pocos cientos o miles de archivos fuente, y la clase en cuestión es bastante utilizada, esto puede significar mucha repetición. Incluso si cada clase solo es utilizada por 2 o 3 personas más, mientras más código haya en el encabezado, más trabajo por hacer.

Si las definiciones de funciones miembro están en una unidad de traducción (archivo .cpp) propia, entonces se compilan una vez, y solo las declaraciones de funciones se compilan varias veces.

Es cierto que las funciones de miembro definidas (no solo declaradas) en la definición de clase son implícitamente inline. Pero inline no significa lo que las personas razonablemente pueden suponer que significa. inline dice que es legal que múltiples definiciones de la función aparezcan en diferentes unidades de traducción, y luego se vinculen entre sí. Esto es necesario si la clase está en un archivo de encabezado que usarán diferentes archivos de origen, por lo que el lenguaje intenta ser útil.

inline es también una pista para el compilador que la función de utilidad podría ser inline, pero a pesar del nombre, eso es opcional. Cuanto más sofisticado sea su compilador, mejor podrá tomar sus propias decisiones sobre la creación de líneas, y tendrá menos pistas. Más importante que la etiqueta en línea real es si la función está disponible para el compilador. Si la función está definida en una unidad de traducción diferente, entonces no está disponible cuando se compila la llamada, y si algo va a alinear la llamada, entonces tendrá que ser el vinculador, no el compilador.

Usted puede ser capaz de ver mejor las diferencias al considerar una tercera forma posible de hacerlo:

// File class.h 
class MyClass 
{ 
    private: 
     //attributes 
    public: 
     void method1(...); 
     void method2(...); 
     ... 
}; 

inline void MyClass::method1(...) 
{ 
    //implementation 
} 

inline void MyClass::method2(...) 
{ 
    //implementation 
} 

Ahora que la línea implícita está fuera del camino, aún existen algunas diferencias entre este "todo cabecera "enfoque y el enfoque de" encabezado más fuente ". La forma de dividir el código entre las unidades de traducción tiene consecuencias sobre lo que sucede a medida que se genera.

+0

de hecho estaba hablando de "solicitud al compilador", sé que es opcional la línea efectiva ... – Jack

+0

Sí, estaba repitiéndolo por si acaso un lector nuevo en C++ se olvidó cuando caminaron por mi primeros tres párrafos. –

7

Cualquier cambio en un encabezado que incluya la implementación obligará a todas las demás clases que incluyen ese encabezado a recompilar y volver a vincular.

Desde cabeceras cambian con menos frecuencia que las implementaciones, poniendo la aplicación en un archivo separado, se puede ahorrar un considerable tiempo de compilación.

Como algunas otras respuestas ya han señalado, sí, la definición de un método dentro del bloque class de un archivo hará que el compilador se alinee.

1

Sí, métodos de definición dentro de la definición de clase es equivalente a declarar que inline. No hay otra diferencia. No hay beneficio en definir todo en el archivo de encabezado.

Algo así como que se ve generalmente en C++ con clases de plantilla, ya que las definiciones miembros de la plantilla tienen que ser incluidos en el archivo de cabecera también (debido al hecho de que la mayoría de los compiladores no admiten export). Pero con las clases normales que no son de plantilla no tiene sentido hacer esto, a menos que realmente desee declarar sus métodos como inline.

+1

¿Por qué se votó negativamente? Parece correcto (e informativo) lo suficiente para mí .... –

+3

Trolling aparentemente. Un downvote sin una explicación siempre es curricán. – AnT

+1

El punto obvio de hacer esto es que es más corto. No es realmente una buena razón para los encabezados públicos, teniendo en cuenta las desventajas. Pero para una pequeña clase de utilidad utilizada internamente dentro de un solo módulo, está perfectamente bien. –

7

Sí, el compilador intentará inline un método declarado directamente en el archivo de cabecera como:

class A 
{ 
public: 
    void method() 
    { 
    } 
}; 

se me ocurre después de comodidades en la separación de la aplicación en los archivos de cabecera:

  1. Usted' No tendré código inflado debido a que el mismo código se incluirá en varias unidades de traducción
  2. Su tiempo de compilación reducirá drásticamente . Recuerde que para cualquier modificación en el archivo de encabezado el compilador tiene que compilar todos los demás archivos que lo incluyen directa o indirectamente . Supongo que será muy frustrante para cualquiera construir el binario completo nuevamente solo para agregar un espacio en el archivo de encabezado.
0

En el pasado, creé un módulo que protegía contra las diferencias en varias distribuciones CORBA y se esperaba que funcionara de manera uniforme en varias combinaciones de OS/compilador/CORBA lib. Al implementarlo en un archivo de cabecera, fue más fácil agregarlo a un proyecto con una simple inclusión. La misma técnica garantizaba que el código se recompilaba al mismo tiempo cuando el código que lo llamaba requería recompilación cuando, por ejemplo, se estaba compilando con una biblioteca diferente o en un sistema operativo diferente.

Así que mi punto es que si tiene una biblioteca pequeña que se espera que sea reutilizable y recompilable en varios proyectos, convertirla en un encabezado ofrece ventajas en la integración con otros proyectos en lugar de agregar archivos adicionales al proyecto principal o recompilando un archivo lib/obj externo.

1

Para mí, la diferencia principal es que un archivo de encabezado es como una "interfaz" para la clase, indicando a los clientes de esa clase cuáles son sus métodos públicos (las operaciones que admite), sin que los clientes se preocupen por la implementación específica de aquellos. En sentido, es una manera de encapsular a sus clientes de los cambios de implementación, porque solo el archivo cpp cambia y, por lo tanto, el tiempo de compilación es mucho menor.

Cuestiones relacionadas