Partilhar via


Funções embutidas (C++)

A palavra-chave inline 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, usar funções embutidas pode tornar seu programa mais rápido porque elimina a sobrecarga associada às chamadas de função. Chamar uma função requer enviar por push o endereço do remetente na pilha, enviar argumentos por push para a pilha, saltar para o corpo da função e executar uma instrução do remetente quando a função for concluída. Esse processo é eliminado ao tornar a função embutida. O compilador também tem diferentes oportunidades para otimizar as funções expandidas embutidas em comparação com aquelas que não são. Uma desvantagem 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 irá embutir uma função se o endereço já estiver em uso ou se o compilador decidir que ele é 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 construtor Account é uma função embutida porque é definida no corpo da declaração de classe. As funções membro GetBalance, Deposit e Withdraw são inline especificadas em suas definições. A palavra-chave inline é 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 especificadores inline e __inline sugerem ao compilador que ele insira uma cópia do corpo da função em cada lugar onde a função é chamada.

A inserção (chamada expansão embutida ou inlining) só ocorrerá se a análise de custo/benefício do próprio compilador demonstrar que ela será vantajosa. 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 poderá ignorá-la. 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.

Esses motivos podem interferir no inlining, assim como outros, 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 /Ob para influenciar se a 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 __forceinline, o compilador não poderá 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 mais usadas em funções pequenas, como aquelas que fornecem acesso aos 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 função inline. 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 melhor coisa 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 2 + 2 * 3 + 3 que é avaliada como 11, mas o resultado esperado é 24. Uma correção aparentemente válida é adicionar parênteses em ambos os argumentos da macro de função, resultando em #define mult2(a, b) (a) * (b), o que resolverá o problema em questão, mas ainda poderá 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 #define mult3(a, b) ((a) * (b)).
  • Uma função embutida está sujeita ao processamento semântico pelo compilador, enquanto o pré-processador expande as macros sem esse mesmo benefício. As macros não são fortemente tipadas, 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 retornasse 6, portanto, a expressão é avaliada como 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 é intencional. Se a função embutida square fosse usada, a função increment seria chamada apenas uma vez e o resultado correto de 25 seria obtido.

Confira também

noinline
auto_inline