Funciones insertadas (C++)

La inline palabra clave sugiere que el compilador sustituya el código dentro de la definición de función en lugar de cada llamada a esa función.

En teoría, el uso de funciones insertadas puede hacer que el programa sea más rápido porque eliminan la sobrecarga asociada a las llamadas de función. Llamar a una función requiere insertar la dirección de retorno en la pila, insertar argumentos en la pila, saltar al cuerpo de la función y, a continuación, ejecutar una instrucción de devolución cuando finalice la función. Este proceso se elimina mediante la inserción de la función . El compilador también tiene distintas oportunidades para optimizar las funciones expandidas en línea frente a las que no lo están. Una compensación de las funciones insertadas es que el tamaño general del programa puede aumentar.

La sustitución de código insertado se realiza a discreción del compilador. Por ejemplo, el compilador no insertará una función si se toma su dirección o si el compilador decide que es demasiado grande.

Una función definida en el cuerpo de una declaración de clase es una función insertada implícitamente.

Ejemplo

En la siguiente declaración de clase, el Account constructor es una función insertada porque se define en el cuerpo de la declaración de clase. Las funciones GetBalancemiembro , Deposity Withdraw se especifican inline en sus definiciones. La inline palabra clave es opcional en las declaraciones de función de la declaración de clase.

// account.h
class Account
{
public:
    Account(double initial_balance)
    {
        balance = initial_balance;
    }

    double GetBalance() const;
    double Deposit(double amount);
    double Withdraw(double amount);

private:
    double balance;
};

inline double Account::GetBalance() const
{
    return balance;
}

inline double Account::Deposit(double amount)
{
    balance += amount;
    return balance;
}

inline double Account::Withdraw(double amount)
{
    balance -= amount;
    return balance;
}

Nota:

En la declaración de clase, las funciones se declaran sin la palabra clave inline. La palabra clave inline se puede especificar en la declaración de clase; el resultado es el mismo.

Una función de miembro insertada determinada se debe declarar de la misma manera en cada unidad de compilación. Debe haber exactamente una definición de una función insertada.

Una función miembro de clase utiliza de manera predeterminada una vinculación externa, a menos que una definición de esa función contenga el especificador inline. En el ejemplo anterior se muestra que no es necesario declarar estas funciones explícitamente con el especificador inline. El uso inline de en la definición de función sugiere al compilador que se trata como una función insertada. Sin embargo, no puede volver a declarar una función como inline después de una llamada a esa función.

inline, __inline y __forceinline

Los inline especificadores y __inline sugieren al compilador que inserta una copia del cuerpo de la función en cada lugar al que se llama a la función.

La inserción, denominada expansióno inserción insertada, solo se produce si el propio análisis de costo-beneficio del compilador muestra que vale la pena. La expansión alineada minimiza la sobrecarga de las llamadas a función con el costo potencial de un tamaño de código mayor.

La palabra clave __forceinline invalida el análisis de costos y beneficios, y deja la decisión en manos del programador. Hay que ser prudentes al utilizar __forceinline. El uso indiscriminado de __forceinline puede dar lugar a código mayor con unas mejoras de rendimiento mínimas o, en algunos casos, incluso con pérdidas de rendimiento (debido a la mayor paginación que supone un archivo ejecutable mayor, por ejemplo).

El compilador trata las opciones de expansión insertada y las palabras clave como sugerencias. No se garantiza que las funciones se inserten. No se puede forzar que el compilador inserte una función determinada, incluso con la palabra clave __forceinline. Al compilar con /clr, el compilador no insertará una función si se aplican atributos de seguridad a la función.

A efectos de compatibilidad con versiones anteriores, _inline y _forceinline son sinónimos de __inline y __forceinline, respectivamente, a menos que se especifique la opción del compilador/Za (Deshabilitar extensiones de lenguaje).

La palabra clave inline indica al compilador que se prefiere la expansión alineada. Sin embargo, el compilador puede omitirlo. Hay dos casos en los que este comportamiento puede ocurrir:

  • Funciones recursivas.
  • Funciones a las que se hace referencia mediante un puntero en otra parte de la unidad de traducción.

Estos motivos pueden interferir con la inserción, como otros, según lo determinado por el compilador. No dependa del inline especificador para que se inserte una función.

En lugar de expandir una función insertada definida en un archivo de encabezado, el compilador puede crearla como una función invocable en más de una unidad de traducción. El compilador marca la función generada para el enlazador para evitar infracciones de una regla de definición (ODR).

Al igual que con las funciones normales, no hay ningún orden definido para la evaluación de argumentos en una función insertada. De hecho, podría ser diferente del orden de evaluación de los argumentos cuando se pasan mediante el protocolo normal de llamadas a función.

Use la opción de optimización del /Ob compilador para influir en si realmente se produce la expansión de funciones insertadas.
/LTCG inserta entre módulos si se solicita en el código fuente o no.

Ejemplo 1

// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
    return a < b ? b : a;
}

Las funciones miembro de una clase se pueden declarar alineadas mediante la palabra clave inline o colocando la definición de función dentro de la definición de clase.

Ejemplo 2

// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>

class MyClass
{
public:
    void print() { std::cout << i; }   // Implicitly inline

private:
    int i;
};

Específico de Microsoft

La palabra clave __inline es equivalente a inline.

Incluso con __forceinline, el compilador no puede insertar una función si:

  • La función o su llamador se compila con /Ob0 (la opción predeterminada para las compilaciones de depuración).
  • La función y el llamador utilizan tipos diferentes de control de excepciones (control de excepciones de C++ en uno y control de excepciones estructurado en otro).
  • La función tiene una lista de argumentos de variable.
  • La función utiliza el ensamblado insertado, a menos que se compile con /Ox, /O1 o /O2.
  • La función es recursiva y no tiene #pragma inline_recursion(on) establecida. Con la directiva pragma, las funciones recursivas se alinean hasta una profundidad predeterminada de 16 llamadas. Para reducir la profundidad de inserción, utilice la directiva pragma inline_depth.
  • La función es virtual y se llama a la misma de forma virtual. Se pueden insertar llamadas directas a funciones virtuales.
  • El programa toma la dirección de la función y la llamada se realiza a través del puntero a la función. Se pueden insertar llamadas directas a funciones cuyas direcciones se han tomado.
  • La función está marcada también con el modificador naked__declspec.

Si el compilador no puede insertar una función declarada con __forceinline, genera una advertencia de nivel 1, excepto cuando:

  • La función se compila mediante /Od o /Ob0. No se espera ninguna inserción en estos casos.
  • La función se define externamente, en una biblioteca incluida u otra unidad de traducción, o es un destino de llamada virtual o un destino de llamada indirecto. El compilador no puede identificar código no insertado que no se encuentra en la unidad de traducción actual.

Se pueden sustituir las funciones recursivas con código insertado hasta una profundidad especificada por la directiva pragma inline_depth, hasta un máximo de 16 llamadas. Después de dicha profundidad, las llamadas a función recursivas se tratan como llamadas a una instancia de la función. La profundidad a la que la heurística de alineación examina las funciones recursivas no puede ser superior a 16. La directiva pragma inline_recursion controla la expansión alineada de una función que se está expandiendo actualmente. Vea la opción del compilador Expansión de funciones insertadas (/Ob) para obtener información relacionada.

FIN de Específicos de Microsoft

Para obtener más información sobre cómo utilizar el especificador inline, vea:

Cuándo usar funciones insertadas

Las funciones insertadas se usan mejor para funciones pequeñas, como las que proporcionan acceso a los miembros de datos. Las funciones cortas son sensibles a la sobrecarga de llamadas a función. Las funciones más largas tardan proporcionalmente menos tiempo en la secuencia de llamada y devolución y se benefician menos de la inserción.

Una clase Point se puede definir de la siguiente manera:

// when_to_use_inline_functions.cpp
// compile with: /c
class Point
{
public:
    // Define "accessor" functions
    // as reference types.
    unsigned& x();
    unsigned& y();

private:
    unsigned _x;
    unsigned _y;
};

inline unsigned& Point::x()
{
    return _x;
}

inline unsigned& Point::y()
{
    return _y;
}

Si se da por hecho que la manipulación coordinada es una operación relativamente común en un cliente de esta clase, especificar las dos funciones de descriptor de acceso (x y y en el ejemplo anterior) como inline normalmente evita la sobrecarga en:

  • Llamadas de función (incluidos el paso de parámetros y la colocación de la dirección del objeto en la pila)
  • Conservación del marco de pila del llamador
  • Nueva configuración del marco de pila
  • Comunicación de valor devuelto
  • Restauración del marco de pila antiguo
  • Return

Funciones insertadas frente a macros

Una macro tiene algunas cosas en común con una inline función. Pero hay diferencias importantes. Considere el ejemplo siguiente:

#include <iostream>

#define mult1(a, b) a * b
#define mult2(a, b) (a) * (b)
#define mult3(a, b) ((a) * (b))

inline int multiply(int a, int b)
{
    return a * b;
}

int main()
{
    std::cout << (48 / mult1(2 + 2, 3 + 3)) << std::endl; // outputs 33
    std::cout << (48 / mult2(2 + 2, 3 + 3)) << std::endl; // outputs 72
    std::cout << (48 / mult3(2 + 2, 3 + 3)) << std::endl; // outputs 2
    std::cout << (48 / multiply(2 + 2, 3 + 3)) << std::endl; // outputs 2

    std::cout << mult3(2, 2.2) << std::endl; // no warning
    std::cout << multiply(2, 2.2); // Warning C4244	'argument': conversion from 'double' to 'int', possible loss of data
}
33
72
2
2
4.4
4

Estas son algunas de las diferencias entre la macro y la función insertada:

  • Las macros siempre se expanden en línea. Sin embargo, una función insertada solo está insertada cuando el compilador determina que es lo óptimo que se debe hacer.
  • La macro puede dar lugar a un comportamiento inesperado, lo que puede provocar errores sutiles. Por ejemplo, la expresión mult1(2 + 2, 3 + 3) se expande a 2 + 2 * 3 + 3 la que se evalúa como 11, pero el resultado esperado es 24. Una corrección aparentemente válida es agregar paréntesis alrededor de ambos argumentos de la macro de función, lo que dará como resultado #define mult2(a, b) (a) * (b), lo que resolverá el problema a mano, pero todavía puede causar un comportamiento sorprendente cuando parte de una expresión mayor. Esto se mostró en el ejemplo anterior y el problema podría solucionarse definiendo la macro como tal #define mult3(a, b) ((a) * (b)).
  • Una función insertada está sujeta al procesamiento semántico por parte del compilador, mientras que el preprocesador expande macros sin esa misma ventaja. Las macros no son seguras para tipos, mientras que las funciones son.
  • Las expresiones pasadas como argumentos a funciones insertadas se evalúan una vez. En algunos casos, las expresiones pasadas como argumentos a macros se pueden evaluar más de una vez. Por ejemplo, considere lo siguiente:
#include <iostream>

#define sqr(a) ((a) * (a))

int increment(int& number)
{
    return number++;
}

inline int square(int a)
{
    return a * a;
}

int main()
{
    int c = 5;
    std::cout << sqr(increment(c)) << std::endl; // outputs 30
    std::cout << c << std::endl; // outputs 7

    c = 5;
    std::cout << square(increment(c)) << std::endl; // outputs 25
    std::cout << c; // outputs 6
}
30
7
25
6

En este ejemplo, se llama a la función increment dos veces que la expresión sqr(increment(c)) se expande a ((increment(c)) * (increment(c))). Esto provocó la segunda invocación de increment para devolver 6, por lo que la expresión se evalúa como 30. Cualquier expresión que contenga efectos secundarios puede afectar al resultado cuando se usa en una macro, examine la macro totalmente expandida para comprobar si el comportamiento está previsto. En su lugar, si se usó la función square insertada, solo se llamaría a la función increment una vez y se obtendrá el resultado correcto de 25.

Consulte también

noinline
auto_inline