Por que as funções embutidas do C ++ estão no cabeçalho?

120

NB: Esta não é uma questão sobre como usar funções inline ou como elas funcionam, mas por que são feitas da maneira que são.

A declaração de uma função de membro de classe não precisa definir uma função inline, pois é apenas a implementação real da função. Por exemplo, no arquivo de cabeçalho:

struct foo{
    void bar(); // no need to define this as inline
}

Então, por que a implementação em linha de um aulas funcionam têm de estar no arquivo de cabeçalho? Por que não consigo colocar a função embutida no .cpparquivo? Se eu tentasse colocar a definição embutida no .cpparquivo, receberia um erro nas linhas 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
fonte
@Charles Eu diria que o segundo link pode ser semelhante, mas estou perguntando mais sobre a lógica por trás de por que o inline funciona da maneira que funciona.
thecoshman de
2
Nesse caso, acho que você pode ter entendido mal tanto "inline" quanto "header files"; nenhuma das suas afirmações é verdadeira. Você pode ter uma implementação embutida de uma função de membro e pode colocar definições de função embutida em um arquivo de cabeçalho, mas pode não ser uma boa ideia. Você pode esclarecer sua dúvida?
CB Bailey de
Pós-edição, acho que você pode estar perguntando sobre situações em que inlineaparece uma definição, mas não uma declaração anterior vs vice-versa . Em caso afirmativo, isso pode ajudar: stackoverflow.com/questions/4924912/…
CB Bailey

Respostas:

122

A definição de uma inlinefunção não precisa estar em um arquivo de cabeçalho, mas, por causa da regra de definição única ( ODR ) para funções inline, uma definição idêntica para a função deve existir em cada unidade de tradução que a usa.

A maneira mais fácil de fazer isso é colocar a definição em um arquivo de cabeçalho.

Se você deseja colocar a definição de uma função em um único arquivo de origem, não deve declará-la inline. Uma função não declarada inlinenão significa que o compilador não possa embutir a função.

Se você deve declarar uma função inlineou não, geralmente é uma escolha que você deve fazer com base em qual versão das regras de definição faz mais sentido seguir; adicionar inlinee ser restringido pelas restrições subsequentes faz pouco sentido.

CB Bailey
fonte
Mas o compilador não compila o arquivo .cpp, que inclui os arquivos .h ... de forma que, quando compilar um arquivo .cpp, ele tenha tanto a desaceleração quanto os arquivos de origem. Outros arquivos de cabeçalho puxados são apenas seus para que o compilador possa 'confiar' que essas funções existem e serão implementadas em algum outro arquivo de origem
thecoshman
1
Esta é realmente uma resposta muito melhor do que a minha, +1minha!
sbi de
2
@thecoshman: Existem duas distinções. Arquivo de origem versus arquivo de cabeçalho. Por convenção, um arquivo de cabeçalho geralmente se refere a um arquivo de origem que não é a base para uma unidade de tradução, mas apenas #incluído de outros arquivos de origem. Depois, há declaração versus definição. Você pode ter declarações ou definições de funções em arquivos de cabeçalho ou arquivos de origem 'normais'. Infelizmente, não tenho certeza do que você está perguntando em seu comentário.
CB Bailey de
não se preocupe, agora entendo por que é ... embora eu não tenha certeza de quem realmente respondeu a esta. Uma combinação sua e da resposta de @Xanatos explicou isso para mim.
thecoshman de
113

Existem duas maneiras de ver isso:

  1. As funções embutidas são definidas no cabeçalho porque, para embutir uma chamada de função, o compilador deve ser capaz de ver o corpo da função. Para um compilador ingênuo fazer isso, o corpo da função deve estar na mesma unidade de tradução da chamada. (Um compilador moderno pode otimizar as unidades de tradução e, portanto, uma chamada de função pode ser embutida, mesmo que a definição da função esteja em uma unidade de tradução separada, mas essas otimizações são caras, nem sempre estão habilitadas e nem sempre são suportadas pelo compilador)

  2. as funções definidas no cabeçalho devem ser marcadas inlineporque, caso contrário, cada unidade de tradução que inclui o cabeçalho conterá uma definição da função, e o vinculador reclamará sobre várias definições (uma violação da Regra de Uma Definição). A inlinepalavra-chave suprime isso, permitindo que várias unidades de tradução contenham definições (idênticas).

As duas explicações realmente se resumem ao fato de que a inlinepalavra - chave não faz exatamente o que você esperava.

Um compilador C ++ é livre para aplicar a otimização inlining (substituir uma chamada de função pelo corpo da função chamada, economizando a sobrecarga da chamada) a qualquer momento, desde que não altere o comportamento observável do programa.

A inlinepalavra-chave torna mais fácil para o compilador aplicar essa otimização, permitindo que a definição da função seja visível em várias unidades de tradução, mas usar a palavra-chave não significa que o compilador tenha que embutir a função, e não usar a palavra-chave não proíba o compilador de embutir a função.

Jalf
fonte
23

Este é um limite do compilador C ++. Se você colocar a função no cabeçalho, todos os arquivos cpp onde ela pode ser embutida podem ver a "fonte" de sua função e a sequência pode ser feita pelo compilador. Caso contrário, o inlining teria que ser feito pelo vinculador (cada arquivo cpp é compilado em um arquivo obj separadamente). O problema é que seria muito mais difícil fazer isso no vinculador. Um problema semelhante existe com classes / funções de "modelo". Eles precisam ser instanciados pelo compilador, porque o vinculador teria problemas para instanciar (criar uma versão especializada) deles. Algum compilador / vinculador mais recente pode fazer uma compilação / vinculação de "duas passagens", onde o compilador faz uma primeira passagem, então o vinculador faz seu trabalho e chama o compilador para resolver coisas não resolvidas (embutidos / modelos ...)

xanatos
fonte
Ah eu vejo! sim, não é para a classe em si que usa a função inline, seu outro código que faz uso das funções inline. Eles só veem o arquivo de cabeçalho da classe que está sendo embutida!
thecoshman de
11
Não concordo com essa resposta, não é um limite do compilador C ++; é puramente como as regras de idioma são especificadas. As regras de linguagem permitem um modelo de compilação simples, mas não proíbem implementações alternativas.
CB Bailey de
3
Eu concordo com @Charles. Na verdade, existem compiladores que funcionam em linha em unidades de tradução, então isso definitivamente não é devido às limitações do compilador.
sbi de
5
Embora essa resposta pareça ter alguns erros técnicos, ela me ajudou a ver como o compilador funciona com arquivos de cabeçalho e outros.
thecoshman de
10

O motivo é que o compilador precisa realmente ver a definição para poder inseri-la no lugar da chamada.

Lembre-se de que C e C ++ usam um modelo de compilação muito simplista, onde o compilador sempre vê apenas uma unidade de tradução por vez. (Falha na exportação, que é o principal motivo pelo qual apenas um fornecedor o implementou.)

sbi
fonte
9

A inlinepalavra-chave c ++ é enganosa, não significa "embutir esta função". Se uma função for definida como embutida, significa simplesmente que pode ser definida várias vezes, desde que todas as definições sejam iguais. É perfeitamente válido para uma função marcada inlinepara ser uma função real que é chamada em vez de obter o código embutido no ponto onde é chamada.

Definir uma função em um arquivo de cabeçalho é necessário para modelos, uma vez que, por exemplo, uma classe com modelo não é realmente uma classe, é um modelo para uma classe da qual você pode fazer várias variações. Para que o compilador seja capaz de, por exemplo, fazer uma Foo<int>::bar()função quando você usa o modelo Foo para criar uma classe Foo , a definição real de Foo<T>::bar()deve estar visível.

Erik
fonte
E, como é um modelo para uma classe , não é chamado de classe de modelo , mas sim de modelo de classe .
sbi de
4
O primeiro parágrafo está completamente correto (e eu gostaria de enfatizar "enganoso"), mas não vejo a necessidade do non sequitur em modelos.
Thomas Edleson de
Alguns compiladores irão usá-lo como uma dica de que a função provavelmente pode ser sequencial, mas, de fato, não é garantido que ela seja sequenciada apenas porque você a declara inline(nem a não declaração inlinegarante que ela não será sequencial).
Keith M de
4

Eu sei que este é um tópico antigo, mas pensei que deveria mencionar a externpalavra - chave. Recentemente, encontrei esse problema e resolvi da seguinte maneira

Helper.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
fonte
6
Isso geralmente não resultará na função realmente sendo embutida, a menos que você esteja usando Whole Program Optimization (WPO).
Chuck Walbourn
3

Porque o compilador precisa vê-los para colocá-los em linha . E os arquivos de cabeçalho são os "componentes" normalmente incluídos em outras unidades de tradução.

#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
fonte
1

Funções Inline

Em C ++, uma macro nada mais é do que uma função embutida. SO agora as macros estão sob controle do compilador.

  • Importante : Se definirmos uma função dentro da classe, ela se tornará Inline automaticamente

O código da função Inline é substituído no local em que é chamado, reduzindo assim a sobrecarga da função de chamada.

Em alguns casos, a função embutida pode não funcionar, como

  • Se a variável estática for usada dentro da função embutida.

  • Se a função é complicada.

  • Se chamada recursiva de função

  • Se o endereço da função for tomado implícita ou explicitamente

Função definida fora da classe conforme abaixo pode se tornar embutida

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

A função definida dentro da classe também se torna embutida

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

Aqui, as funções getSpeed ​​e setSpeed ​​ficarão embutidas

Saurabh Raoot
fonte
eh, talvez alguma informação interessante, mas não tenta explicar o porquê . Talvez você saiba, mas não está deixando isso claro.
thecoshman de
2
A seguinte afirmação não é verdadeira: "Importante: Se definirmos uma função dentro da classe, ela se tornará Inline automaticamente" Nem mesmo se você escrever "inline" na declaração / definição, você pode ter certeza de que está de fato sendo inline. Nem mesmo para modelos. Talvez você quisesse dizer que o compilador assume automaticamente a palavra-chave "inline", mas não precisa segui-la e o que percebi é que na maioria dos casos ele não inline tais definições no cabeçalho, nem mesmo para funções constexpr simples com aritmética básica.
Pablo Ariel
Olá, obrigado pelos comentários ... Abaixo estão as linhas de Pensando em C ++ micc.unifi.it/bertini/download/programmazione/… Página 400 .. Por favor, verifique .. Por favor, vote positivamente se você concorda. Obrigado ..... Inlines dentro das classes Para definir uma função inline, você deve normalmente preceder a definição da função com a palavra-chave inline. No entanto, isso não é necessário dentro de uma definição de classe. Qualquer função que você definir dentro de uma definição de classe é automaticamente embutida.
Saurabh Raoot
Os autores desse livro podem reivindicar o que quiserem, porque escrevem livros e não códigos. Isso é algo que eu tive que analisar profundamente para fazer minhas demos 3D portáteis caberem em menos de 64kb, evitando o código embutido tanto quanto possível. Programar trata de fatos e não de religião, então realmente não importa se algum "deus programador" disse isso em um livro se não representar o que acontece na prática. E a maioria dos livros C ++ por aí contém uma coleção de maus conselhos, nos quais de vez em quando você encontra algum truque legal para adicionar ao seu repertório.
Pablo Ariel
Olá @PabloAriel Obrigado ... Por favor analise e me diga .. Estou ok para atualizar esta resposta de acordo com a análise
Saurabh Raoot