programacion programa otro incluir encabezado definicion crear cabecera archivos archivo c++ header-files

c++ - programa - incluir header en c



¿Cómo puede un archivo de cabecera de C++ incluir implementación? (6)

Ok, no soy un experto en C / C ++, pero pensé que el punto de un archivo de encabezado era declarar las funciones, luego el archivo C / CPP era definir la implementación.

El verdadero propósito de un archivo de encabezado es compartir código entre varios archivos de origen. Se usa comúnmente para separar las declaraciones de las implementaciones para una mejor administración del código, pero eso no es un requisito. Es posible escribir código que no se basa en archivos de encabezado, y es posible escribir código que se compone solo de archivos de encabezado (las bibliotecas STL y Boost son buenos ejemplos de eso). Recuerde, cuando el preprocesador encuentra una declaración #include , reemplaza la declaración con el contenido del archivo al que se hace referencia, entonces el compilador solo ve el código preprocesado completado.

Así, por ejemplo, si tiene los siguientes archivos:

Foo.h:

#ifndef FooH #define FooH class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; #endif

Foo.cpp:

#include "Foo.h" UInt32 Foo::GetNumberChannels() const { return _numberChannels; }

Bar.cpp:

#include "Foo.h" Foo f; UInt32 chans = f.GetNumberChannels();

El preprocesador analiza Foo.cpp y Bar.cpp por separado y produce el siguiente código que luego analiza el compilador :

Foo.cpp:

class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; UInt32 Foo::GetNumberChannels() const { return _numberChannels; }

Bar.cpp:

class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; Foo f; UInt32 chans = f.GetNumberChannels();

Bar.cpp se compila en Bar.obj y contiene una referencia para llamar a Foo::GetNumberChannels() . Foo.cpp se compila en Foo.obj y contiene la implementación real de Foo::GetNumberChannels() . Después de compilar, el enlazador luego compara los archivos .obj y los une para producir el ejecutable final.

Entonces, ¿por qué hay una implementación en un encabezado?

Al incluir la implementación del método dentro de la declaración del método, se declara implícitamente como en línea (hay una palabra clave en inline real que también se puede usar explícitamente). Indicar que el compilador debe incluir una función en línea es solo una sugerencia que no garantiza que la función realmente se incorporará. Pero si lo hace, entonces desde donde se llame la función en línea, el contenido de la función se copia directamente en el sitio de la llamada, en lugar de generar una instrucción CALL para saltar a la función y saltar a la persona que llama al salir. El compilador puede entonces tomar en cuenta el código circundante y optimizar el código copiado, si es posible.

¿Tiene que ver con la palabra clave const?

No. La palabra clave const simplemente le indica al compilador que el método no alterará el estado del objeto al que se está llamando en tiempo de ejecución.

¿Cuál es exactamente el beneficio / punto de hacerlo de esta manera en lugar de definir la implementación en el archivo CPP?

Cuando se usa efectivamente, le permite al compilador producir código de máquina más rápido y mejor optimizado.

Ok, no soy un experto en C / C ++, pero pensé que el punto de un archivo de encabezado era declarar las funciones, luego el archivo C / CPP era definir la implementación.

Sin embargo, al revisar algunos códigos en C ++ esta noche, encontré esto en el archivo de encabezado de una clase ...

public: UInt32 GetNumberChannels() const { return _numberChannels; } // <-- Huh?? private: UInt32 _numberChannels;

Entonces, ¿por qué hay una implementación en un encabezado? ¿Tiene que ver con la palabra clave const ? ¿Eso inscribe un método de clase? ¿Cuál es exactamente el beneficio / punto de hacerlo de esta manera en lugar de definir la implementación en el archivo CPP?


Es perfectamente válido tener una implementación de una función en un archivo de encabezado. El único problema con esto es romper la regla de una definición. Es decir, si incluye el encabezado de varios otros archivos, obtendrá un error del compilador.

Sin embargo, hay una excepción. Si declara que una función está en línea, está exenta de la regla de una definición. Esto es lo que está sucediendo aquí, ya que las funciones miembro definidas dentro de una definición de clase están implícitamente en línea.

La propia línea es una sugerencia para el compilador de que una función puede ser un buen candidato para la línea. Es decir, expandiendo cualquier llamada a la misma en la definición de la función, en lugar de una simple llamada a la función. Esta es una optimización que cambia el tamaño del archivo generado para un código más rápido. En los compiladores modernos, la mayoría de las veces se ignora proporcionar esta sugerencia de alineación para una función, excepto por los efectos que tiene en la regla de una definición. Además, un compilador siempre es libre de incluir en línea cualquier función que considere adecuada, incluso si no se ha declarado en inline (explícita o implícitamente).

En su ejemplo, el uso de const después de la lista de argumentos indica que la función miembro no modifica el objeto en el que se llama. En la práctica, esto significa que el objeto señalado por this , y por extensión todos los miembros de la clase, se considerarán const . Es decir, intentar modificarlos generará un error en tiempo de compilación.


Incluso en C simple, es posible poner código en un archivo de encabezado. Si lo hace, generalmente necesita declararlo static o, de lo contrario, varios archivos .c, incluido el mismo encabezado, causarán un error de "función definida múltiple".

El preprocesador incluye textualmente un archivo de inclusión, por lo que el código de un archivo de inclusión se convierte en parte del archivo de origen (al menos desde el punto de vista del compilador).

Los diseñadores de C ++ querían habilitar la programación orientada a objetos con buena ocultación de datos, por lo que esperaban ver muchas funciones de captador y definidor. No querían una penalización de rendimiento irrazonable. Por lo tanto, diseñaron C ++ para que los captadores y los definidores no solo pudieran ser declarados en el encabezado, sino que realmente se implementaran, para que estuvieran en línea. Esa función que mostró es un captador, y cuando se compile ese código C ++, no habrá ninguna llamada de función; El código para obtener ese valor solo se compilará en su lugar.

Es posible crear un lenguaje informático que no tenga la distinción entre el archivo de encabezado y el archivo de origen, sino que solo tenga "módulos" reales que el compilador entiende. (C ++ no hizo eso; simplemente construyeron sobre el exitoso modelo C de los archivos de origen y los archivos de encabezado incluidos textualmente). Si los archivos de origen son módulos, es posible que un compilador extraiga el código del módulo y luego en línea ese código. Pero la forma en que C ++ lo hizo es más simple de implementar.


Mantener la implementación en el archivo de encabezado de clase funciona, como estoy seguro de que sabe si compiló su código. La palabra clave const garantiza que no cambie ningún miembro, mantiene la instancia immutable durante la llamada del método.


Por lo que sé, hay dos tipos de métodos, que pueden implementarse de manera segura dentro del archivo de encabezado.

  • Métodos en línea: su implementación se copia en los lugares donde se utilizan, por lo que no hay problemas con los errores del enlazador de doble definición;
  • Métodos de plantilla: en realidad se compilan en el momento de la creación de instancias de la plantilla (por ejemplo, cuando alguien ingresa un tipo en lugar de plantilla), por lo que, nuevamente, no hay posibilidad de un problema de doble definición.

Creo que tu ejemplo encaja con el primer caso.


Se declara implícitamente en inline en virtud de ser una función miembro definida dentro de la declaración de clase. Esto no significa que el compilador tenga que integrarlo, pero significa que no romperá la one-definition-rule . No tiene ninguna relación con const * . Tampoco está relacionado con la longitud y la complejidad de la función.

Si fuera una función que no es miembro, entonces tendría que declararla explícitamente como en inline :

inline void foo() { std::cout << "foo!/n"; }

* Vea here para más información sobre const al final de una función miembro.