¿Por qué hay funciones en línea de C ++ en el encabezado?

120

NB Esta no es una pregunta sobre cómo usar las funciones en línea o cómo funcionan, sino por qué se hacen de la forma en que están.

La declaración de una función miembro de clase no necesita definir una función como inline, es solo 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 tiene que estar en el archivo de encabezado? ¿Por qué no puedo poner la función en línea en el .cpparchivo? Si intentara poner la definición en línea en el .cpparchivo, obtendría un error en las líneas de:

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
thecoshman
fuente
@Charles Diría que el segundo enlace puede ser similar, pero estoy preguntando más sobre la lógica detrás de por qué en línea funciona de la manera en que lo hace.
thecoshman
2
En ese caso, creo que puede haber entendido mal "archivos en línea" o "archivos de encabezado"; ninguna de sus afirmaciones es cierta. Puede tener una implementación en línea de una función miembro y puede poner definiciones de función en línea en un archivo de encabezado, es solo que puede que no sea una buena idea. ¿Puede aclarar su pregunta?
CB Bailey
Después de la edición, creo que puede estar preguntando sobre situaciones en las que inlineaparece en una definición pero no en una declaración previa y viceversa . Si es así, esto puede ayudar: stackoverflow.com/questions/4924912/…
CB Bailey

Respuestas:

122

La definición de una inlinefunción no tiene que estar en un archivo de encabezado pero, debido a la regla de una definición ( ODR ) para funciones en línea, debe existir una definición idéntica para la función en cada unidad de traducción que la use.

La forma más sencilla de lograr esto es poniendo la definición en un archivo de encabezado.

Si desea poner la definición de una función en un solo archivo fuente, no debe declararla inline. Una función no declarada inlineno significa que el compilador no pueda alinear la función.

Si debe declarar una función inlineo no, suele ser una elección que debe tomar en función de la versión de las reglas de una definición que tenga más sentido seguir; agregar inliney luego ser restringido por las restricciones subsiguientes tiene poco sentido.

CB Bailey
fuente
Pero el compilador no compila el archivo .cpp, que incluye los archivos .h ... para que cuando compile un archivo .cpp tenga tanto la desaceleración como los archivos fuente. Otros archivos de encabezado extraídos son solo suyos para que el compilador pueda 'confiar' en que esas funciones existen y se implementarán en algún otro archivo fuente
thecoshman
1
¡Esta es en realidad una respuesta mucho mejor que la mía, +1de mi parte!
sbi
2
@thecoshman: Hay dos distinciones. Archivo de origen vs archivo de encabezado. Por convención, un archivo de encabezado generalmente se refiere a un archivo de origen que no es la base para una unidad de traducción, sino que solo se #incluye desde otros archivos de origen. Luego está la declaración frente a la definición. Puede tener declaraciones o definiciones de funciones en archivos de encabezado o archivos fuente "normales". Me temo que no estoy seguro de lo que pregunta en su comentario.
CB Bailey
no se preocupe, entiendo por qué es ahora ... aunque no estoy seguro de quién respondió realmente a esta. Una combinación de la tuya y la respuesta de @ Xanatos me lo explicó.
thecoshman
113

Hay dos formas de verlo:

  1. Las funciones en línea se definen en el encabezado porque, para insertar 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 en todas las unidades de traducción, por lo que una llamada de función puede estar insertada aunque la definición de la 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 el compilador)

  2. las funciones definidas en el encabezado deben marcarse inlineporque 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 múltiples definiciones (una violación de la Regla de una definición). La inlinepalabra clave suprime esto, lo que permite que varias unidades de traducción contengan definiciones (idénticas).

Las dos explicaciones realmente se reducen al hecho de que la inlinepalabra clave no hace exactamente lo que esperarías.

Un compilador de C ++ es libre de aplicar la optimización en línea (reemplazar una llamada de función con el cuerpo de la función llamada, ahorrando la sobrecarga de la llamada) en cualquier momento que quiera, siempre que no altere el comportamiento observable del programa.

La inlinepalabra clave hace que sea más fácil para el 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 usar la palabra clave no significa que el compilador tenga que alinear la función, y no usar la palabra clave no lo hace. prohíbe al compilador insertar la función.

jalf
fuente
23

Este es un límite del compilador de C ++. Si coloca la función en el encabezado, todos los archivos cpp donde se puede insertar pueden ver la "fuente" de su función y el compilador puede realizar la inserción. De lo contrario, el enlazador debería realizar la inserción (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". El compilador debe crear una instancia de ellos, porque el enlazador tendría problemas para instanciarlos (crear una versión especializada de). Algún compilador / enlazador más nuevo puede hacer una compilación / enlace de "dos pasadas" 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 ...)

xanatos
fuente
¡Oh ya veo! sí, no es para la clase en sí que usa la función en línea, su otro código que hace uso de las funciones en línea. ¡Solo ven el archivo de encabezado de la clase que se está insertando!
thecoshman
11
No estoy de acuerdo con esta respuesta, no es un límite del compilador de C ++; es simplemente cómo se especifican las reglas del idioma. Las reglas del lenguaje permiten un modelo de compilación simple pero no prohíben implementaciones alternativas.
CB Bailey
3
Estoy de acuerdo con @Charles. De hecho, hay compiladores que funcionan en línea en todas las unidades de traducción, por lo que definitivamente esto no se debe a las limitaciones del compilador.
sbi
5
Si bien esta respuesta parece tener algunos errores técnicos, me ayudó a ver cómo funciona el compilador con archivos de encabezado y demás.
thecoshman
10

La razón es que el compilador tiene que ver 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 ve solo 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 lo implementó).

sbi
fuente
9

La inlinepalabra clave 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 se puede definir varias veces siempre que todas las definiciones sean iguales. Es perfectamente legal que una función marcada inlinesea ​​una función real a la que se llama en lugar de insertar código en el punto donde se llama.

Es necesario definir una función en un archivo de encabezado 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 puedes hacer múltiples variaciones. Para que el compilador pueda, por ejemplo, hacer una Foo<int>::bar()función cuando usa la plantilla Foo para crear una clase Foo , la definición real de Foo<T>::bar()debe estar visible.

Erik
fuente
Y dado que es una plantilla para una clase , no se llama clase de plantilla , sino plantilla de clase .
sbi
4
El primer párrafo es completamente correcto (y desearía poder enfatizar "engañoso"), pero no veo la necesidad de que las plantillas no sean sequitur.
Thomas Edleson
Algunos compiladores lo usarán como una pista de que la función probablemente pueda estar en línea, pero de hecho, no se garantiza que esté en línea solo porque usted la declara inline(ni no declararla inlinegarantiza que no estará en línea).
Keith M
4

Sé que este es un hilo antiguo, pero pensé que debería mencionar la externpalabra clave. Recientemente me encontré con este problema y lo resolví de la siguiente manera

Ayudante.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());
        }
    }
}
flamewave000
fuente
6
Por lo general, esto no dará como resultado que la función se inserte realmente a menos que esté utilizando la optimización completa del programa (WPO).
Chuck Walbourn
3

Porque el compilador necesita verlos para integrarlos . Y los archivos de encabezados son los "componentes" que se incluyen comúnmente 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.
Leandro TC Melo
fuente
1

Funciones en línea

En C ++, una macro no es más que una función en línea. Así que ahora las macros están bajo el control del compilador.

  • Importante : si definimos una función dentro de la clase, se convertirá automáticamente en Inline

El código de la función Inline se reemplaza en el lugar donde se llama, por lo que reduce la sobrecarga de la función de llamada.

En algunos casos, la función Inlining no puede funcionar, como

  • Si se usa una variable estática dentro de la función en línea.

  • Si la función es complicada.

  • Si la llamada de función recursiva

  • Si la dirección de la función se toma implícita o explícitamente

La función definida fuera de la clase como se muestra a continuación puede volverse en línea

inline int AddTwoVar(int x,int y); //This may not become inline 

inline int AddTwoVar(int x,int y) { return x + y; } // This becomes inline

La función definida dentro de la clase también se vuelve en línea

// Inline SpeedMeter functions
class SpeedMeter
{
    int speed;
    public:
    int getSpeed() const { return speed; }
    void setSpeed(int varSpeed) { speed = varSpeed; }
};
int main()
{
    SpeedMeter objSM;
    objSM.setSpeed(80);
    int speedValue = A.getSpeed();
} 

Aquí las funciones getSpeed ​​y setSpeed ​​se convertirán en inline

Saurabh Raoot
fuente
eh, quizás alguna buena información, pero realmente no intenta explicar por qué . Tal vez lo haga, pero no lo deja claro.
thecoshman
2
La siguiente declaración no es cierta: "Importante: Si definimos una función dentro de la clase, se convertirá automáticamente en Inline". Incluso si escribe "inline" en la declaración / definición, puede estar seguro de que de hecho está inline. Ni siquiera para plantillas. Tal vez quisiste decir que el compilador asume automáticamente la palabra clave "en línea", pero no tiene que seguirla y lo que he notado es que en la mayoría de los casos no inserta esas definiciones en el encabezado, ni siquiera para funciones simples constexpr con aritmética básica.
Pablo Ariel
Hola, gracias por los comentarios ... A continuación se muestran las líneas de Thinking in C ++ micc.unifi.it/bertini/download/programmazione/… Página 400 .. Por favor, marque .. Por favor, vote si está de acuerdo. Gracias ..... Inlines dentro de las clases Para definir una función en línea, normalmente debe preceder la definición de la función con la palabra clave en línea. Sin embargo, esto no es necesario dentro de una definición de clase. Cualquier función que defina dentro de una definición de clase es automáticamente una en línea.
Saurabh Raoot
Los autores de ese libro pueden reclamar lo que quieran, porque escriben libros y no codifican. Esto es algo que tuve que analizar en profundidad para que mis demostraciones 3D portátiles quepan en menos de 64 kb evitando el código en línea tanto como sea posible. La programación se trata de hechos y no de religión, por lo que realmente no importa si algún "programador de Dios" lo dijo en un libro si no representa lo que sucede en la práctica. Y la mayoría de los libros de C ++ tienen una colección de malos consejos, en los que de vez en cuando puedes encontrar algún truco ingenioso para agregar a tu repertorio.
Pablo Ariel
Hola @PabloAriel Gracias ... Por favor analice y avíseme ... Estoy bien para actualizar esta respuesta según el análisis
Saurabh Raoot