todas - ¿Por qué las funciones en línea de C++ están en el encabezado?
tipos de funciones en c (7)
Este es un límite del compilador de C ++. Si coloca la función en el encabezado, todos los archivos cpp donde puede estar en línea pueden ver el "origen" de su función y el compilador puede hacer la alineación. De lo contrario, la vinculación debería ser hecha por el enlazador (cada archivo cpp se compila en un archivo obj por separado). El problema es que sería mucho más difícil hacerlo en el enlazador. Existe un problema similar con las clases / funciones de "plantilla". Necesitan ser instanciados por el compilador, porque el enlazador tendría problemas para crear instancias (creando una versión especializada de) ellos. Algún compilador / enlazador más nuevo puede hacer una compilación / enlace de "dos pasos" donde el compilador hace una primera pasada, luego el enlazador hace su trabajo y llama al compilador para resolver cosas no resueltas (en línea / plantillas ...)
Nota: esta no es una pregunta sobre cómo usar las funciones en línea o cómo funcionan, más por qué se hacen de la manera que son.
La declaración de una función miembro de clase no necesita definir una función como en inline
, solo es la implementación real de la función. Por ejemplo, en el archivo de encabezado:
struct foo{
void bar(); // no need to define this as inline
}
Entonces, ¿por qué la implementación en línea de una función de clases debe estar en el archivo de encabezado? ¿Por qué no puedo poner la función en línea en el archivo .cpp
? Si intentara poner la definición en línea en el archivo .cpp
obtendría un error del siguiente .cpp
:
error LNK2019: unresolved external symbol
"public: void __thiscall foo::bar(void)"
(?bar@foo@@QAEXXZ) referenced in function _main
1>C:/Users/Me/Documents/Visual Studio 2012/Projects/inline/Debug/inline.exe
: fatal error LNK1120: 1 unresolved externals
Hay dos formas de verlo:
Las funciones en línea se declaran en el encabezado porque, para alinear una llamada de función, el compilador debe poder ver el cuerpo de la función. Para que un compilador ingenuo haga eso, el cuerpo de la función debe estar en la misma unidad de traducción que la llamada. (Un compilador moderno puede optimizar a través de unidades de traducción, por lo que una llamada a función puede estar incluida aunque la definición de función esté en una unidad de traducción separada, pero estas optimizaciones son costosas, no siempre están habilitadas y no siempre fueron compatibles con compilador)
las funciones declaradas en el encabezado deben marcarse en
inline
porque de lo contrario, cada unidad de traducción que incluye el encabezado contendrá una definición de la función, y el enlazador se quejará de las definiciones múltiples (una violación de la regla de una sola definición). La palabra clave eninline
suprime esto, permitiendo que múltiples unidades de traducción contengan definiciones (idénticas).
Las dos explicaciones realmente se reducen al hecho de que la palabra clave en inline
no hace exactamente lo que cabría esperar.
Un compilador C ++ es libre de aplicar la optimización interna (reemplace una llamada a función con el cuerpo de la función llamada, guardando la sobrecarga de llamada) en el momento que quiera, siempre y cuando no altere el comportamiento observable del programa.
La palabra clave en inline
facilita al compilador aplicar esta optimización al permitir que la definición de la función sea visible en múltiples unidades de traducción, pero el uso de la palabra clave no significa que el compilador tenga que alinear la función y no usar la palabra clave doesn '' t prohibir al compilador que incluya la función.
La definición de una función en inline
no tiene que estar en un archivo de encabezado, pero, debido a la regla de una definición para las funciones en línea, debe existir una definición idéntica para cada función en cada unidad de traducción que la utiliza.
La forma más fácil de lograr esto es colocar la definición en un archivo de encabezado.
Si desea poner la definición de una función en un único archivo fuente, no debe declararlo en inline
. Una función no declarada en inline
no significa que el compilador no puede alinear la función.
Si debe declarar una función en inline
o no, suele ser una elección que debe realizar en función de la versión de las reglas de definición que tenga más sentido para usted; agregar inline
y luego restringirlo por las restricciones subsiguientes tiene poco sentido.
La palabra clave en inline
c ++ es engañosa, no significa "en línea esta función". Si una función se define como en línea, simplemente significa que puede definirse varias veces siempre que todas las definiciones sean iguales. Es perfectamente legal que una función marcada en inline
sea una función real a la que se llama en lugar de recibir código en línea en el punto en que se llama.
La definición de una función en un archivo de encabezado es necesaria para las plantillas, ya que, por ejemplo, una clase con plantilla no es realmente una clase, es una plantilla para una clase de la que puede hacer múltiples variaciones. Para que el compilador pueda, por ejemplo, hacer una función Foo<int>::bar()
cuando usa la plantilla Foo para crear una clase Foo , la definición real de Foo<T>::bar()
debe estar visible .
La razón es que el compilador tiene que ver realmente la definición para poder colocarla en lugar de la llamada.
Recuerde que C y C ++ usan un modelo de compilación muy simplista, donde el compilador siempre solo ve una unidad de traducción a la vez. (Esto falla para la exportación, que es la razón principal por la que solo un proveedor realmente lo implementó).
Porque el compilador necesita verlos para alinearlos . Y los archivos de encabezados son los "componentes" que comúnmente se incluyen en otras unidades de traducción.
#include "file.h"
// Ok, now me (the compiler) can see the definition of that inline function.
// So I''m able to replace calls for the actual implementation.
Sé que este es un hilo viejo, pero pensé que debería mencionar que la palabra clave extern
. Recientemente me encontré con este problema y lo resolví de la siguiente manera
Helper.h
namespace DX
{
extern inline void ThrowIfFailed(HRESULT hr);
}
Helper.cpp
namespace DX
{
inline void ThrowIfFailed(HRESULT hr)
{
if (FAILED(hr))
{
std::stringstream ss;
ss << "#" << hr;
throw std::exception(ss.str().c_str());
}
}
}