Partilhar via


Funções embutidas (C++)

A inline palavra-chave sugere que o compilador substitua o código dentro da definição de função no lugar de cada chamada para essa função.

Em teoria, o uso de funções embutidas pode tornar seu programa mais rápido porque eliminam a sobrecarga associada às chamadas de função. Chamar uma função requer empurrar o endereço de retorno na pilha, empurrar argumentos para a pilha, pular para o corpo da função e, em seguida, executar uma instrução de retorno quando a função terminar. Este processo é eliminado pela inclusão da função. O compilador também tem diferentes oportunidades para otimizar funções expandidas em linha versus aquelas que não são. Uma compensação das funções embutidas é que o tamanho geral do seu programa pode aumentar.

A substituição de código embutido é feita a critério do compilador. Por exemplo, o compilador não embutirá uma função se seu endereço for tomado ou se o compilador decidir que é muito grande.

Uma função definida no corpo de uma declaração de classe é, implicitamente, uma função embutida.

Exemplo

Na declaração de classe a seguir, o Account construtor é uma função embutida porque é definido no corpo da declaração de classe. As funções GetBalancedo membro , Deposite Withdraw são especificadas inline em suas definições. A inline palavra-chave é opcional nas declarações de função na declaração de classe.

// 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;
}

Observação

Na declaração de classe, as funções foram declaradas sem a palavra-chave inline. A palavra-chave inline pode ser especificada na declaração de classe; o resultado é o mesmo.

Uma determinada função de membro embutida deve ser declarada da mesma maneira em cada unidade de compilação. Deve haver exatamente uma definição de uma função embutida.

Uma função membro de classe usa como padrão o vínculo externo a menos que uma definição dessa função contenha o especificador inline. O exemplo anterior mostra que você não precisa declarar essas funções explicitamente com o especificador inline. Usar inline na definição de função sugere ao compilador que ele seja tratado como uma função embutida. No entanto, você não pode redeclarar uma função como inline após uma chamada a essa função.

inline, __inline, e __forceinline

Os inline especificadores e __inline sugerem ao compilador que ele insira uma cópia do corpo da função em cada lugar em que a função é chamada.

A inserção, chamada de expansão em linha ou inlining, ocorre somente se a própria análise de custo-benefício do compilador mostrar que vale a pena. A expansão embutida reduz a sobrecarga da chamada de função sobre os possíveis custos de um código maior.

A palavra-chave __forceinline substitui a análise de custo/benefício e, em vez disso, se baseia na opinião do programador. Tenha cuidado ao usar __forceinline. A utilização indiscriminada de __forceinline pode resultar em código maior com ganhos de desempenho marginais apenas ou, em alguns casos, mesmo perdas de desempenho (devido ao aumento de paginação de um executável maior, por exemplo).

O compilador trata as opções de expansão embutida e as palavras-chave como sugestões. Não há garantia de que as funções serão embutidas. Você não pode forçar o compilador a embutir uma função específica, mesmo com a palavra-chave __forceinline. Ao compilar com /clr, o compilador não embutirá uma função se houver atributos de segurança aplicados a ela.

Para compatibilidade com versões anteriores, _inline e _forceinline são sinônimos para __inline e __forceinline, respectivamente, a menos que a opção do compilador /Za (Desabilitar extensões de linguagem) esteja especificada.

A palavra-chave inline informa ao compilador que a expansão embutida é preferida. No entanto, o compilador pode ignorá-lo. Os dois casos em que esse comportamento pode acontecer são:

  • Funções recursivas.
  • Funções às quais são feita referência por meio de um ponteiro em outro lugar na unidade de tradução.

Essas razões podem interferir no inlineing, assim como outras, conforme determinado pelo compilador. Não dependa do inline especificador para fazer com que uma função seja embutida.

Em vez de expandir uma função embutida definida em um arquivo de cabeçalho, o compilador pode criá-la como uma função callable em mais de uma unidade de tradução. O compilador marca a função gerada para o vinculador para evitar violações de ODR (regra definição única).

Assim como acontece com as funções normais, não há nenhuma ordem definida para avaliação de argumentos em uma função embutida. De fato, ela pode ser diferente da ordem na qual os argumentos são avaliados quando passados usando o protocolo comum de chamada de função.

Use a opção de otimização do compilador para influenciar se a /Ob expansão da função embutida realmente ocorre.
/LTCG faz o inlining entre módulos, seja ele solicitado no código-fonte ou não.

Exemplo 1

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

As funções membro de uma classe podem ser declaradas de maneira embutida usando a palavra-chave inline ou colocando a definição de função na definição da classe.

Exemplo 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 da Microsoft

A palavra-chave __inline é equivalente a inline.

Mesmo com __forceinlineo , o compilador não pode embutir uma função se:

  • A função ou o chamador dela forem compilados com /Ob0 (a opção padrão para builds de depuração).
  • A função e o chamador usam tipos diferentes de manipulação de exceções (manipulação de exceções do C++ em uma, manipulação de exceções estruturada no outro).
  • A função tem uma lista de argumentos variável.
  • A função usa o assembly embutido, a menos que compilada com /Ox, /O1 ou /O2.
  • A função é recursiva e não tem #pragma inline_recursion(on) definido. Com o pragma, as funções recursivas são embutidas em uma profundidade padrão de 16 chamadas. Para reduzir a profundidade do inlining, use o pragma inline_depth.
  • A função é virtual e é chamada virtualmente. Chamadas diretas à funções virtuais podem ser embutidas.
  • O programa usa o endereço da função e a chamada à função é feita pelo ponteiro. Chamadas diretas a funções que tiveram o endereço removido podem ser embutidas.
  • A função também está marcada com o modificador naked__declspec.

Se o compilador não puder embutir uma função declarada com __forceinline, ele gerará um aviso de nível 1, exceto quando:

  • A função é compilada usando /Od ou /Ob0. Nenhum inlining é esperado nesses casos.
  • A função é definida externamente, em uma biblioteca incluída ou em outra unidade de tradução, ou é um destino de chamada virtual ou um destino de chamada indireto. O compilador não pode identificar o código não embutido que ele não pode encontrar na unidade de tradução atual.

As funções embutidas recursivas podem ser substituídas por código embutido com profundidade especificada pelo pragma inline_depth, até um máximo de 16 chamadas. Após essa profundidade, as chamadas de função recursivas são tratadas como chamadas a uma instância da função. A profundidade até a qual as funções recursivas são examinadas por heurística embutida não pode exceder 16. O pragma inline_recursion controla a expansão embutida de uma função atualmente em expansão. Confira a opção de compilador Expansão de função embutida (/Ob) para obter informações relacionadas.

Fim da seção específica da Microsoft

Para obter mais informações sobre como usar o especificador inline, confira:

Quando usar funções embutidas

As funções embutidas são melhor usadas para funções pequenas, como aquelas que fornecem acesso a membros de dados. As funções curtas são sensíveis à sobrecarga de chamadas de função. As funções mais longas passam proporcionalmente menos tempo na sequência de chamadas/retornos e se beneficiam menos do inlining.

Uma classe Point pode ser definida da seguinte maneira:

// 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;
}

Supondo que a manipulação coordenada seja uma operação relativamente comum em um cliente dessa classe, especificar as duas funções de acesso (x e y no exemplo acima) como inline normalmente poupa a sobrecarga de:

  • Chamadas de função (inclusive passagem de parâmetros e colocação do endereço do objeto na pilha)
  • Preservação do registro de ativação do chamador
  • Configuração do novo registro de ativação
  • Comunicação do valor de retorno
  • Restaurando o registro de ativação antigo
  • Return

Funções embutidas versus macros

Uma macro tem algumas coisas em comum com uma inline função. Mas há diferenças importantes. Considere o seguinte exemplo:

#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

Aqui estão algumas das diferenças entre a macro e a função embutida:

  • As macros são sempre expandidas em linha. No entanto, uma função embutida só é embutida quando o compilador determina que é a coisa ideal a fazer.
  • A macro pode resultar em comportamento inesperado, o que pode levar a bugs sutis. Por exemplo, a expressão mult1(2 + 2, 3 + 3) se expande para o que avalia para 2 + 2 * 3 + 3 11, mas o resultado esperado é 24. Uma correção aparentemente válida é adicionar parênteses em torno de ambos os argumentos da macro de função, resultando em , que resolverá o problema em #define mult2(a, b) (a) * (b)questão, mas ainda pode causar um comportamento surpreendente quando parte de uma expressão maior. Isso foi demonstrado no exemplo anterior, e o problema poderia ser resolvido definindo a macro como tal #define mult3(a, b) ((a) * (b)).
  • Uma função embutida está sujeita ao processamento semântico pelo compilador, enquanto o pré-processador expande macros sem esse mesmo benefício. As macros não são seguras para o tipo, enquanto as funções são.
  • As expressões transmitidas como argumentos para as funções integradas são avaliadas uma única vez. Em alguns casos, as expressões transmitidas como argumentos para macros podem ser avaliadas mais de uma vez. Considere o seguinte exemplo:
#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

Neste exemplo, a função increment é chamada duas vezes à medida que a expressão sqr(increment(c)) se expande para ((increment(c)) * (increment(c))). Isso fez com que a segunda invocação de increment retornar 6, daí a expressão avaliar para 30. Qualquer expressão que contenha efeitos colaterais pode afetar o resultado quando usada em uma macro, examine a macro totalmente expandida para verificar se o comportamento é pretendido. Em vez disso, se a função embutida fosse usada, a função squareincrement seria chamada apenas uma vez e o resultado correto de 25 seria obtido.

Confira também

noinline
auto_inline