Quando devo escrever a palavra-chave 'inline' para uma função / método?

562

Quando devo escrever a palavra-chave inlinepara uma função / método em C ++?

Depois de ver algumas respostas, algumas perguntas relacionadas:

  • Quando não devo escrever a palavra-chave 'inline' para uma função / método em C ++?

  • Quando o compilador não sabe quando criar uma função / método 'inline'?

  • Importa se um aplicativo é multithread quando se escreve 'inline' para uma função / método?

Parcial
fonte
40
Se você definir uma função em um cabeçalho, precisará declará-la em linha. Caso contrário, você receberá erros do vinculador sobre várias definições da função.
1813 Martin Martin
15
@ Martin: A menos que esteja em uma definição de classe, seja exigente.
22615 David Thornley
20
@ David: Para ser mais exigente, isso é apenas porque essas funções estão implicitamente marcadas inline(9.3 / 2).
Lightness Races in Orbit
Relacionado: Faz algum sentido usar palavras-chave embutidas com modelos?
precisa saber é o seguinte
Consulte também Funções embutidas nas Perguntas frequentes sobre C ++. Eles têm um tratamento muito bom de inline.
jww 23/07/19

Respostas:

882

Oh cara, uma das minhas irritações.

inlineé mais staticou externmenos uma diretiva dizendo ao compilador para incorporar suas funções. extern, static, inlineSão directivas de ligação, usado quase que exclusivamente pelo vinculador, não o compilador.

Diz-se que inlinesugere ao compilador que você acha que a função deve estar embutida. Isso pode ter sido verdade em 1998, mas uma década depois o compilador não precisa dessas dicas. Sem mencionar que os seres humanos geralmente estão errados quando se trata de otimizar código, portanto a maioria dos compiladores ignora a 'dica'.

  • static- o nome da variável / função não pode ser usado em outras unidades de tradução. O Linker precisa garantir que não use acidentalmente uma variável / função definida estaticamente de outra unidade de tradução.

  • extern- use este nome de variável / função nesta unidade de tradução, mas não reclame se não estiver definido. O vinculador irá resolvê-lo e garantir que todo o código que tentou usar algum símbolo externo tenha seu endereço.

  • inline- esta função será definida em várias unidades de tradução, não se preocupe. O vinculador precisa garantir que todas as unidades de conversão usem uma única instância da variável / função.

Nota: Geralmente, declarar modelos não inlinefaz sentido, pois eles já têm a semântica de ligação inline. No entanto, especialização explícita e instanciação de modelos precisaminline ser usadas.


Respostas específicas para suas perguntas:

  • Quando devo escrever a palavra-chave 'inline' para uma função / método em C ++?

    Somente quando você deseja que a função seja definida em um cabeçalho. Mais exatamente, apenas quando a definição da função pode aparecer em várias unidades de tradução. É uma boa idéia definir funções pequenas (como em um liner) no arquivo de cabeçalho, pois fornece ao compilador mais informações para trabalhar enquanto otimiza seu código. Também aumenta o tempo de compilação.

  • Quando não devo escrever a palavra-chave 'inline' para uma função / método em C ++?

    Não adicione inline apenas porque você acha que seu código será executado mais rapidamente se o compilador o incorporar.

  • Quando o compilador não sabe quando criar uma função / método 'inline'?

    Geralmente, o compilador poderá fazer isso melhor do que você. No entanto, o compilador não tem a opção de incorporar código se não tiver a definição de função. No código otimizado ao máximo, geralmente todos os privatemétodos são incorporados, independentemente de você solicitar ou não.

    Como um aparte para impedir a inclusão no GCC, use __attribute__(( noinline ))e no Visual Studio, use __declspec(noinline).

  • Importa se um aplicativo é multithread quando se escreve 'inline' para uma função / método?

    O multithreading não afeta o inlining de forma alguma.

deft_code
fonte
172
+1 Melhor descrição do inline que eu já vi em ... (para sempre). Agora vou enganá-lo e usá-lo em todas as minhas explicações sobre a palavra-chave inline.
Martin Iorque
6
@ Ziggy, o que eu estava tentando dizer era que o compilador embutido e a inlinepalavra - chave não estão relacionadas. Você tem a idéia certa. Por via de regra, adivinhar o que seria melhorado com a inserção é muito propenso a erros. A exceção a essa regra é um liners.
Deft_code 15/08/11
4
Essa resposta me confunde um pouco. Você diz tudo isso sobre o compilador ser capaz de alinhar / não alinhar melhor as coisas. Então você diz que deve colocar um liners / pequenas funções no cabeçalho e que o compilador não pode embutir o código sem a definição da função. Isso não é um pouco contraditório? Por que não colocar tudo no arquivo cpp e deixar o compilador decidir?
user673679
5
O compilador apenas chamará funções em linha onde a definição estiver disponível no site da chamada. Deixar todas as funções no arquivo cpp limitaria o inlining a esse arquivo. Sugiro definir pequenos liners de linha embutidos no .h, pois o custo para a velocidade de compilação é insignificante e você tem quase certeza de que o compilador fará a chamada na linha. O que eu quero dizer sobre a inclusão do compilador é que ele é uma porta da arte negra da otimização, na qual o seu compilador é muito melhor do que você.
Deft_code
8
Sempre que leio algo sobre o conhecimento acumulado da Internet, tenho de pensar na famosa citação de John Lawton: A ironia da Era da Informação é que ela deu nova respeitabilidade à opinião desinformada.
11ns13pectable IIns13
60

Eu gostaria de contribuir para todas as ótimas respostas neste tópico com um exemplo convincente para dispersar qualquer mal-entendido restante.

Dados dois arquivos de origem, como:

  • inline111.cpp:

    #include <iostream>
    
    void bar();
    
    inline int fun() {
      return 111;
    }
    
    int main() {
      std::cout << "inline111: fun() = " << fun() << ", &fun = " << (void*) &fun;
      bar();
    }
  • inline222.cpp:

    #include <iostream>
    
    inline int fun() {
      return 222;
    }
    
    void bar() {
      std::cout << "inline222: fun() = " << fun() << ", &fun = " << (void*) &fun;
    }

  • Caso A:

    Compilar :

    g++ -std=c++11 inline111.cpp inline222.cpp

    Saída :

    inline111: fun() = 111, &fun = 0x4029a0
    inline222: fun() = 111, &fun = 0x4029a0

    Discussão :

    1. Mesmo que você deva ter definições idênticas de suas funções embutidas, o compilador C ++ não o sinaliza se esse não for o caso (na verdade, devido à compilação separada, não há como verificar). É seu próprio dever garantir isso!

    2. O Linker não reclama da regra de definição única , como fun()é declarado inline. No entanto, como inline111.cpp é a primeira unidade de conversão (que na verdade chama fun()) processada pelo compilador, o compilador instancia fun()seu primeiro encontro de chamada no inline111.cpp . Se o compilador decidir não expandir fun()sua chamada de qualquer outro lugar no seu programa ( por exemplo, de inline222.cpp ), a chamada para fun()sempre será vinculada à sua instância produzida a partir de inline111.cpp (a chamada para fun()dentro de inline222.cpptambém pode produzir uma instância nessa unidade de tradução, mas permanecerá desvinculada). De fato, isso é evidente nas &fun = 0x4029a0impressões idênticas .

    3. Por fim, apesar da inlinesugestão ao compilador de expandir o one-liner fun(), ele ignora completamente sua sugestão, o que é claro porque fun() = 111nas duas linhas.


  • Caso B:

    Compilar (observe a ordem inversa) :

    g++ -std=c++11 inline222.cpp inline111.cpp

    Saída :

    inline111: fun() = 222, &fun = 0x402980
    inline222: fun() = 222, &fun = 0x402980

    Discussão :

    1. Neste caso, afirma que foram discutidos em Caso A .

    2. Observe um ponto importante: se você comentar a chamada real fun()em inline222.cpp ( por exemplo, comentar completamente a coutdeclaração em inline222.cpp ), então, apesar da ordem de compilação de suas unidades de tradução, fun()será instanciado no seu primeiro contato em inline111.cpp , resultando na impressão do caso B como inline111: fun() = 111, &fun = 0x402980.


  • Caso C:

    Compilar (aviso -O2) :

    g++ -std=c++11 -O2 inline222.cpp inline111.cpp

    ou

    g++ -std=c++11 -O2 inline111.cpp inline222.cpp

    Saída :

    inline111: fun() = 111, &fun = 0x402900
    inline222: fun() = 222, &fun = 0x402900

    Discussão :

    1. Conforme descrito aqui , a -O2otimização incentiva o compilador a expandir realmente as funções que podem ser incorporadas (observe também que -fno-inlineé o padrão sem as opções de otimização). Como é evidente na cópia impressa aqui, fun()ela foi realmente expandida em linha (de acordo com sua definição nessa unidade de tradução específica ), resultando em duas impressões diferentes fun() . Apesar disso, ainda existe apenas uma instância globalmente vinculada fun()(conforme exigido pelo padrão), como é evidente em impressões idênticas &fun .
Alaroff
fonte
8
Sua resposta é um post ilustrativo do porquê a linguagem faz com que essas inlinefunções sejam um comportamento indefinido.
R Sahu
Você também deve adicionar casos em que a compilação e a vinculação são separadas, cada uma .cppsendo sua própria unidade de tradução. De preferência, adicione casos para -fltoativado / desativado.
syockit
A referência C ++ diz explicitamente "Se uma função ou variável embutida (desde C ++ 17) com ligação externa é definida de maneira diferente em diferentes unidades de conversão, o comportamento é indefinido.". Portanto, o que você escreveu é específico do GCC, pois é um efeito colateral da orquestração dos processos de compilação e vinculação. Além disso, observe que isso pode variar entre as versões.
Petr Fiedler
27

Você ainda precisa alinhar explicitamente sua função ao fazer a especialização de modelo (se a especialização estiver no arquivo .h)

BostonLogan
fonte
21

1) Hoje em dia, praticamente nunca. Se for uma boa ideia incorporar uma função, o compilador fará isso sem a sua ajuda.

2) sempre. Veja # 1.

(Editado para refletir que você dividiu sua pergunta em duas perguntas ...)

Aric TenEyck
fonte
Sim. A linha é apenas uma dica para o compilador e é livre para ignorá-lo. Hoje em dia, o compilador provavelmente sabe melhor do que o programador quais funções são melhores para alinhar.
22630 Mark Byers
1
Sim, mas é menos relevante - para que uma função seja incorporada, seu corpo deve estar na mesma unidade de compilação (por exemplo, em um cabeçalho). Isso é menos comum em programas em C.
22611 Michael Kohne
1
a definição de um modelo de função não membro (também conhecido como modelo de função não estática) não requer inline. Veja uma regra de definição (3.2 / 5).
Deft_code 18/11/2009
2
-1: inlineainda é necessário, por exemplo, para definir uma função em um arquivo de cabeçalho (e isso é necessário para incorporar essa função em várias unidades de compilação).
Melebius
1
@ Etienne, que é específica da implementação. Por padrão, há uma regra de definição única, o que significa aqui que, se você incluir ingenuamente a definição de função em várias unidades de conversão, receberá um erro. Mas se essa função tiver inlineespecificador, suas instâncias serão automaticamente recolhidas em uma pelo vinculador e o ODR não será usado.
Ruslan #
12

Quando não devo escrever a palavra-chave 'inline' para uma função / método em C ++?

Se a função for declarada no cabeçalho e definida no .cpparquivo, você não deve escrever a palavra-chave.

Quando o compilador não sabe quando criar uma função / método 'inline'?

Não existe tal situação. O compilador não pode tornar uma função embutida. Tudo o que pode fazer é alinhar algumas ou todas as chamadas para a função. Não é possível fazer isso se não tiver o código da função (nesse caso, o vinculador precisa fazê-lo, se puder).

Importa se um aplicativo é multithread quando se escreve 'inline' para uma função / método?

Não, isso não importa.

Johannes Schaub - litb
fonte
Há casos em que é apropriado usar inline em um arquivo .cpp. Por exemplo, aplicar otimizações ao código que é totalmente específico da implementação.
Robin Davies
Resposta atualizada @RobinDavies. Parece que você não entendeu o que eu queria escrever.
Johannes Schaub - litb
5
  • Quando o compilador não sabe quando criar uma função / método 'inline'?

Isso depende do compilador usado. Não confie cegamente que hoje em dia os compiladores sabem melhor do que os humanos como incorporar e você nunca deve usá-lo por motivos de desempenho, porque é a diretiva de ligação e não a dica de otimização. Embora eu concorde que ideologicamente esses argumentos sejam corretos, encontrar a realidade pode ser uma coisa diferente.

Depois de ler vários threads, tentei, por curiosidade, os efeitos do inline no código que estou trabalhando e os resultados foram que eu obtive uma aceleração mensurável para o GCC e nenhuma aceleração para o compilador Intel.

(Mais detalhes: simulações matemáticas com poucas funções críticas definidas fora da classe, GCC 4.6.3 (g ++ -O3), ICC 13.1.0 (icpc -O3); adicionar inline a pontos críticos causou + 6% de aceleração com o código GCC).

Portanto, se você qualificar o GCC 4.6 como um compilador moderno, o resultado é que a diretiva inline ainda será importante se você escrever tarefas intensivas da CPU e souber exatamente onde está o gargalo.

meda beda
fonte
6
Gostaria de ver mais evidências para fazer backup de suas reivindicações. Forneça o código com o qual você está testando e a saída do assembler com e sem a palavra-chave embutida. Qualquer número de coisas poderia ter lhe proporcionado benefícios de desempenho.
Void.pointer 13/05
1
Finalmente, alguém que não apenas repete o que os outros dizem, mas realmente verifica essas afirmações. O Gcc ainda considera a palavra-chave inline como uma dica (acho que o clang a ignora completamente).
MikeMB
@ void.pointer: Por que isso é tão difícil de acreditar? Se os otimizadores já eram perfeitos, as novas versões não poderiam melhorar o desempenho do programa. Mas eles fazem regularmente.
MikeMB
3

Na realidade, praticamente nunca. Tudo o que você está fazendo é sugerir que o compilador faça uma determinada função embutida (por exemplo, substitua todas as chamadas para essa função / com seu corpo). Não há garantias, é claro: o compilador pode ignorar a diretiva.

O compilador geralmente fará um bom trabalho ao detectar + otimizar coisas como esta.

DarkSquid
fonte
7
O problema é que inlinehá uma diferença semântica em C ++ (por exemplo, na maneira como várias definições são tratadas), o que é importante em alguns casos (por exemplo, modelos).
Pavel Minaev 18/11/2009
4
inline é usado para resolver casos em que um símbolo tem várias definições. Os modelos, no entanto, já são tratados pelo idioma. Uma exceção é uma função de modelo especializada que não possui mais parâmetros de modelo (modelo <>). Eles são tratados mais como funções do que modelos e, portanto, precisam da palavra-chave inline para vincular.
Deft_code 18/11/2009
2

Por padrão, o gcc não alinha nenhuma função ao compilar sem a otimização ativada. Não conheço o visual studio - deft_code

Eu verifiquei isso no Visual Studio 9 (15.00.30729.01) compilando com / FAcs e observando o código do assembly: O compilador produziu chamadas para funções-membro sem a otimização habilitada no modo de depuração . Mesmo se a função estiver marcada com __forceinline , nenhum código de tempo de execução embutido será produzido.

Jedzia
fonte
1
Activar / Wall ser dito sobre quais funções onde marcados em linha mas não chegou a se inlined
paulm
0

Você deseja colocá-lo no início, antes do tipo de retorno. Mas a maioria dos compiladores ignora. Se ele estiver definido e tiver um bloco de código menor, a maioria dos compiladores o considerará em linha de qualquer maneira.

Jeremy Morgan
fonte
0

A menos que você esteja escrevendo uma biblioteca ou tenha motivos especiais, você pode esquecer inlinee usar a otimização do tempo do link . Isso elimina o requisito de que uma definição de função deva estar em um cabeçalho para ser considerada como embutida nas unidades de compilação, e é exatamente isso que inlinepermite.

(Mas consulte Existe algum motivo para não usar a otimização do tempo do link? )

n.caillou
fonte
0

A palavra-chave inline solicita que o compilador substitua a chamada de função pelo corpo da função, ele primeiro avalia a expressão e depois passa. Reduz a sobrecarga da chamada de função, pois não há necessidade de armazenar o endereço de retorno e a pilha de memória não é necessária para a função. argumentos.

Quando usar:

  • Para melhorar o desempenho
  • Para reduzir a sobrecarga de chamadas.
  • Como é apenas uma solicitação ao compilador, certas funções não serão incorporadas * funções grandes
    • funções com muitos argumentos condicionais
    • código recursivo e código com loops etc.
Sheetal
fonte
Pode ser útil saber que esse não é realmente o caso. O nível de otimização -O0 a - Ofast é o que determina se uma função está embutida ou não. Inline na compilação regular (-O0) não embutirá uma função, independentemente de você usar inlineou não C e C ++. C Inline: stackoverflow.com/a/62287072/7194773 C ++ inline: stackoverflow.com/a/62230963/7194773
Lewis Kelsey
0

C ++ inline é totalmente diferente de C inline .

#include <iostream>
extern inline int i[];
int i [5];
struct c {
  int function (){return 1;} //implicitly inline
  static inline int j = 3; //explicitly inline
};
int main() {
  c j;
  std::cout << i;
}

inlinepor si só afeta o compilador, montador e vinculador. É uma diretiva para o compilador dizendo que apenas emite um símbolo para esta função / dados se for usado na unidade de tradução e, se for, como os métodos de classe, diga ao assembler para armazená-los na seção .section .text.c::function(),"axG",@progbits,c::function(),comdatou .section .bss.i,"awG",@nobits,i,comdatpara dados. As instanciações de modelo também entram em seus próprios grupos de comdat.

Isto segue .section name, "flags"MG, @type, entsize, GroupName[, linkage]. Por exemplo, o nome da seção é .text.c::function(). axGsignifica que a seção é alocável, executável e em um grupo, ou seja, um nome de grupo será especificado (e não há sinalizador M, portanto, nenhum tamanho será especificado); @progbitssignifica que a seção contém dados e não está em branco; c::function()é o nome do grupo e o grupo possuicomdatlinkage, o que significa que em todos os arquivos de objeto, todas as seções encontradas com esse nome de grupo marcadas com comdat serão removidas do executável final, exceto 1, isto é, o compilador garante que haja apenas uma definição na unidade de tradução e, em seguida, pede ao assembler para colocar em seu próprio grupo no arquivo de objeto (1 seção em 1 grupo) e, em seguida, o vinculador garantirá que, se algum arquivo de objeto tiver um grupo com o mesmo nome, inclua apenas um no .exe final. A diferença entre etc pelo montador devido a suas diretivas.inlinee não usar inlineagora é visível para o montador e, como resultado, o vinculador, porque não é armazenado no arquivo regular .dataou.text

static inlineem uma classe significa que é uma definição de tipo e não declaração (permite que um membro estático seja definido na classe) e torne-o inline; agora se comporta como acima.

static inlineno escopo do arquivo afeta apenas o compilador. Isso significa para o compilador: somente emita um símbolo para esta função / dados se for usado na unidade de tradução e faça isso como um símbolo estático comum (armazene em.text /.data sem a diretiva .globl). Para o montador, agora não há diferença entre staticestatic inline

extern inlineé uma declaração que significa que você deve definir esse símbolo na unidade de tradução ou gerar erro do compilador; se estiver definido, trate-o como regular inlinee para o montador e vinculador não haverá diferença entre extern inlinee inline, portanto, este é apenas um protetor de compilador.

extern inline int i[];
extern int i[]; //allowed repetition of declaration with incomplete type, inherits inline property
extern int i[5]; //declaration now has complete type
extern int i[5]; //allowed redeclaration if it is the same complete type or has not yet been completed
extern int i[6]; //error, redeclaration with different complete type
int i[5]; //definition, must have complete type and same complete type as the declaration if there is a declaration with a complete type

O conjunto acima, sem a linha de erro, é recolhido inline int i[5]. Obviamente, se você fez extern inline int i[] = {5};entãoextern seria ignorado devido à definição explícita através da atribuição.

inlineem um espaço para nome, veja isso e isso

Lewis Kelsey
fonte
-1

Ao desenvolver e depurar código, deixe de inlinefora. Isso complica a depuração.

O principal motivo para adicioná-los é ajudar a otimizar o código gerado. Normalmente, isso troca maior espaço de código por velocidade, mas às vezes inlineeconomiza espaço em código e tempo de execução.

Gastar esse tipo de pensamento sobre otimização de desempenho antes da conclusão do algoritmo é otimização prematura .

Wallyk
fonte
12
inlineas funções normalmente não são incorporadas, a menos que sejam compiladas com otimizações, para que não afetem a depuração de forma alguma. Lembre-se de que é uma dica, não uma demanda.
Pavel Minaev 18/11/2009
3
Por padrão, o gcc não alinha nenhuma função ao compilar sem a otimização ativada. Eu não sei sobre o visual studio
deft_code
Eu trabalhei em um enorme projeto g ++ que tinha a depuração ativada. Talvez outras opções o inlinetenham impedido, mas as funções foram incorporadas. Era impossível estabelecer um ponto de interrupção significativo neles.
218 Wallyk
2
ativar a depuração não pára de incluir no gcc. Se alguma otimização foi ativada (-O1 ou superior), o gcc tentará incorporar os casos mais óbvios. Tradicionalmente, o GDB tem dificuldade com pontos de interrupção e construtores, especialmente construtores em linha. Mas isso foi corrigido nas versões recentes (pelo menos 6,7, talvez mais cedo).
Deft_code 18/11/2009
2
A adição inlinenão fará nada para melhorar o código em um compilador moderno, que pode descobrir se é incorporado ou não por si próprio.
22630 David Thornley
-1

Quando alguém deve alinhar:

1.Quando alguém deseja evitar a sobrecarga de coisas que acontecem quando a função é chamada como passagem de parâmetro, transferência de controle, retorno de controle etc.

2.A função deve ser pequena, frequentemente chamada e tornar inline é realmente vantajosa, pois, de acordo com a regra 80-20, tente tornar inline a função que tenha maior impacto no desempenho do programa.

Como sabemos, o inline é apenas uma solicitação para o compilador semelhante ao registro e isso custará o tamanho do código do objeto.

Ashish
fonte
"inline é apenas uma solicitação ao compilador semelhante ao registro" Eles são semelhantes porque não são solicitações ou têm algo a ver com otimização. inlineperdeu seu status como uma dica de otimização, e a maioria dos compiladores o usa apenas para permitir permissões para várias definições - como o IMO deveria. Mais ainda, desde C ++ 11, registerfoi totalmente reprovado por seu significado anterior de 'Eu sei melhor do que o compilador otimizar': agora é apenas uma palavra reservada, sem significado atual.
Underscore_d
@underscore_d: O Gcc ainda ouve inlineaté certo ponto.
MikeMB
-1

A função embutida C ++ é um conceito poderoso que é comumente usado com classes. Se uma função estiver embutida, o compilador coloca uma cópia do código dessa função em cada ponto em que a função é chamada em tempo de compilação.

Qualquer alteração em uma função embutida pode exigir que todos os clientes da função sejam recompilados porque o compilador precisaria substituir todo o código novamente, caso contrário, continuará com a funcionalidade antiga.

Para embutir uma função, coloque a palavra-chave embutida antes do nome da função e defina a função antes que sejam feitas chamadas para a função. O compilador pode ignorar o qualificador em linha, caso a função definida seja mais do que uma linha.

Uma definição de função em uma definição de classe é uma definição de função embutida, mesmo sem o uso do especificador embutido.

A seguir, é apresentado um exemplo, que utiliza a função embutida para retornar no máximo dois números

#include <iostream>

using namespace std;

inline int Max(int x, int y) { return (x > y)? x : y; }

// Main function for the program
int main() {
   cout << "Max (100,1010): " << Max(100,1010) << endl;

   return 0;
}

para mais informações veja aqui .

amirfg
fonte