Diferencia entre implementar una clase dentro de un archivo .h o en un archivo .cpp

StackOverflow https://stackoverflow.com/questions/1809679

  •  05-07-2019
  •  | 
  •  

Pregunta

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 se crea una clase en el encabezado y se implementa en un archivo .cpp efectivo.

Para explicar mejor de qué estoy hablando, me refiero a las diferencias entre el enfoque 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 un enfoque de just-header :

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

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

  ...
};

Puedo obtener la principal diferencia: en el segundo caso, el código se incluye en todos los demás archivos que lo necesiten, generando más instancias de las mismas implementaciones, por lo que hay una redundancia implícita; mientras que en el primer caso, el código se compila solo y luego todas las llamadas referidas al objeto de MyClass están vinculadas a la implementación en class.cpp .

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

¿Fue útil?

Solución

La principal diferencia práctica es que si las definiciones de la función 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 de origen, y la clase en cuestión se usa ampliamente, esto puede significar mucha repetición. Incluso si cada clase es utilizada solo por otras 2 o 3 personas, cuanto más código haya en el encabezado, más trabajo hay que hacer.

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

Es cierto que las funciones miembro definidas (no solo declaradas) en la definición de clase están implícitamente inline . Pero inline no significa lo que la gente podría suponer razonablemente 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. Esto es necesario si la clase está en un archivo de encabezado que usarán diferentes archivos de origen, por lo que el idioma intenta ser útil.

inline también es una sugerencia para el compilador de que la función podría ser útil, pero a pesar del nombre, eso es opcional. Cuanto más sofisticado sea su compilador, mejor podrá tomar sus propias decisiones sobre la incorporación y menos necesidad tendrá de sugerencias. 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, no estará disponible cuando se compile la llamada, y si algo va a alinear la llamada, tendrá que ser el vinculador, no el compilador. / p>

Es posible que pueda ver mejor las diferencias si considera 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, quedan algunas diferencias entre este " todo el encabezado " enfoque, y el " encabezado más fuente " enfoque. La forma en que se divide el código entre las unidades de traducción tiene consecuencias por lo que sucede a medida que se construye.

Otros consejos

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

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

Puedo pensar en las siguientes conveniencias para separar la implementación en los archivos de encabezado:

  1. No tendrá el código hinchado debido a que el mismo código se incluye en varias unidades de traducción
  2. Su tiempo de compilación se reducirá drásticamente Recuerda que para cualquier modificación en el archivo de cabecera compilador tiene que construir todos los demás archivos que directa o indirectamente incluirlo Supongo que será muy frustrante para cualquiera construir el todo binario de nuevo sólo para agregar una espacio en el archivo de encabezado.

Cualquier cambio en un encabezado que incluya la implementación forzará a todas las demás clases que incluyan ese encabezado a volver a compilar y a vincular nuevamente.

Dado que los encabezados cambian con menos frecuencia que las implementaciones, al colocar la implementación en un archivo separado, puede ahorrar un tiempo de compilación considerable.

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

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

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

Para mí, la principal diferencia es que un archivo de encabezado es como una interfaz " " para la clase, informar 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 esos. En sentido, es una forma 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.

Una vez en el pasado, creé un módulo que protegía de las diferencias en varias distribuciones CORBA y se esperaba que funcionara de manera uniforme en varias combinaciones de sistema operativo / compilador / CORBA lib. Hacerlo implementado en un archivo de encabezado hizo que sea más fácil agregarlo a un proyecto con una simple inclusión. La misma técnica garantizaba que el código se recompilara al mismo tiempo cuando el código que lo llamaba requería la recompilación cuando, por ejemplo, se compilaba con una biblioteca diferente o en un sistema operativo diferente.

Mi punto es que si tiene una biblioteca bastante pequeña que se espera que sea reutilizable y compilable en varios proyectos, el encabezado ofrece ventajas en la integración con algunos otros proyectos en lugar de agregar archivos adicionales al proyecto principal o recompilar un archivo externo lib / obj.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top