Perché le funzioni inline C ++ sono nell'intestazione?


120

NB Questa non è una domanda su come usare le funzioni inline o su come funzionano, ma piuttosto sul motivo per cui sono fatte come sono.

La dichiarazione di una funzione membro di una classe non ha bisogno di definire una funzione in inlinequanto è solo l'effettiva implementazione della funzione. Ad esempio, nel file di intestazione:

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

Allora perché l'implementazione inline di una funzione di classi deve essere nel file di intestazione? Perché non posso inserire la funzione inline nel .cppfile? Se dovessi provare a inserire la definizione inline nel .cppfile, riceverei un errore sulla falsariga di:

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



@Charles Direi che il secondo collegamento è simile, ma chiedo di più sulla logica alla base del perché inline funziona in questo modo.
thecoshman

2
In tal caso, penso che potresti aver frainteso "inline" o "file di intestazione"; nessuna delle tue affermazioni è vera. Puoi avere un'implementazione inline di una funzione membro e puoi inserire definizioni di funzione inline in un file di intestazione, è solo che potrebbe non essere una buona idea. Puoi chiarire la tua domanda?
CB Bailey

Dopo la modifica, penso che potresti chiedere informazioni sulle situazioni in cui inlineappare su una definizione ma non su una dichiarazione preventiva e viceversa . Se è così, questo può aiutare: stackoverflow.com/questions/4924912/…
CB Bailey,

Risposte:


122

La definizione di una inlinefunzione non deve essere necessariamente in un file di intestazione ma, a causa della regola dell'una definizione ( ODR ) per le funzioni inline, deve esistere una definizione identica per la funzione in ogni unità di traduzione che la utilizza.

Il modo più semplice per ottenere ciò è inserire la definizione in un file di intestazione.

Se vuoi mettere la definizione di una funzione in un unico file sorgente, non dovresti dichiararla inline. Una funzione non dichiaratainline non significa che il compilatore non possa incorporare la funzione.

Il fatto che tu debba dichiarare inlineo meno una funzione di solito è una scelta che dovresti fare in base alla versione delle regole di definizione che ha più senso per te seguire; aggiungere inlinee poi essere limitati dai vincoli successivi ha poco senso.


Ma il compilatore non compila il file .cpp, che include i file .h ... in modo che quando compila un file .cpp abbia sia la decelerazione che i file sorgente. Altri file di intestazione inseriti sono solo loro, quindi il compilatore può 'fidarsi' che quelle funzioni esistono e saranno implementate in qualche altro file sorgente
thecoshman

1
Questa è in realtà una risposta molto migliore della mia, +1da parte mia!
sbi

2
@thecoshman: ci sono due distinzioni. File di origine vs file di intestazione. Per convenzione, un file di intestazione di solito si riferisce a un file di origine che non è quella base per un'unità di traduzione, ma è #incluso solo da altri file di origine. Poi c'è la dichiarazione contro la definizione. È possibile avere dichiarazioni o definizioni di funzioni nei file di intestazione o nei file di origine "normali". Temo di non essere sicuro di cosa chiedi nel tuo commento.
CB Bailey,

non preoccuparti, capisco perché è adesso ... anche se non sono sicuro di chi abbia risposto veramente a questo. Una combinazione tra la tua e la risposta di @ Xanatos me l'ha spiegato.
thecoshman

113

Ci sono due modi per vederlo:

  1. Le funzioni inline sono definite nell'intestazione perché, per inline una chiamata di funzione, il compilatore deve essere in grado di vedere il corpo della funzione. Affinché un compilatore ingenuo possa farlo, il corpo della funzione deve essere nella stessa unità di traduzione della chiamata. (Un compilatore moderno può ottimizzare tra unità di traduzione, quindi una chiamata di funzione può essere inline anche se la definizione della funzione è in un'unità di traduzione separata, ma queste ottimizzazioni sono costose, non sono sempre abilitate e non sono sempre state supportate dal compilatore)

  2. le funzioni definite nell'intestazione devono essere contrassegnate inlineperché altrimenti, ogni unità di traduzione che include l'intestazione conterrà una definizione della funzione e il linker si lamenterà di più definizioni (una violazione della regola dell'una definizione). La inlineparola chiave lo sopprime, consentendo a più unità di traduzione di contenere definizioni (identiche).

Le due spiegazioni si riducono davvero al fatto che la inlineparola chiave non fa esattamente quello che ti aspetteresti.

Un compilatore C ++ è libero di applicare l' ottimizzazione inlining (sostituire una chiamata di funzione con il corpo della funzione chiamata, risparmiando l'overhead della chiamata) ogni volta che vuole, purché non alteri il comportamento osservabile del programma.

La inlineparola chiave rende più facile per il compilatore applicare questa ottimizzazione, consentendo alla definizione della funzione di essere visibile in più unità di traduzione, ma l'uso della parola chiave non significa che il compilatore debba inline la funzione, e non usare la parola chiave non lo fa proibire al compilatore di integrare la funzione.


23

Questo è un limite del compilatore C ++. Se metti la funzione nell'intestazione, tutti i file cpp in cui può essere inline possono vedere il "sorgente" della tua funzione e l'inlining può essere fatto dal compilatore. Altrimenti l'inlining dovrebbe essere fatto dal linker (ogni file cpp è compilato in un file obj separatamente). Il problema è che sarebbe molto più difficile farlo nel linker. Un problema simile esiste con le classi / funzioni "modello". Devono essere istanziate dal compilatore, perché il linker avrebbe problemi a crearne un'istanza (creando una versione specializzata). Alcuni compilatori / linker più recenti possono eseguire una compilazione / collegamento "in due passaggi" in cui il compilatore esegue un primo passaggio, quindi il linker fa il suo lavoro e chiama il compilatore per risolvere le cose non risolte (inline / templates ...)


Oh, capisco! sì, non è per la classe stessa che usa la funzione inline, il suo altro codice che fa uso delle funzioni inline. Vedono solo il file di intestazione per la classe che viene inline!
thecoshman

11
Non sono d'accordo con questa risposta, non è un limite del compilatore C ++; è semplicemente il modo in cui vengono specificate le regole della lingua. Le regole del linguaggio consentono un semplice modello di compilazione ma non vietano implementazioni alternative.
CB Bailey,

3
Sono d'accordo con @Charles. In effetti ci sono compilatori che inline funzionano attraverso unità di traduzione, quindi questo sicuramente non è dovuto alle limitazioni del compilatore.
sbi

5
Sebbene questa risposta sembri avere alcuni errori tecnici, mi ha aiutato a vedere come funziona il compilatore con i file di intestazione e simili.
thecoshman

10

Il motivo è che il compilatore deve effettivamente vedere la definizione per poterla rilasciare al posto della chiamata.

Ricorda che C e C ++ utilizzano un modello di compilazione molto semplicistico, in cui il compilatore vede sempre solo un'unità di traduzione alla volta. (Questo non riesce per l'esportazione, che è il motivo principale per cui solo un fornitore lo ha effettivamente implementato.)


9

La inlineparola chiave c ++ è fuorviante, non significa "inline this function". Se una funzione è definita come inline, significa semplicemente che può essere definita più volte purché tutte le definizioni siano uguali. È perfettamente legale per una funzione contrassegnata inlinecome una funzione reale che viene chiamata invece di ottenere il codice inline nel punto in cui viene chiamata.

La definizione di una funzione in un file di intestazione è necessaria per i modelli, poiché ad esempio una classe basata su modelli non è realmente una classe, è un modello per una classe di cui è possibile apportare più varianti. Affinché il compilatore sia in grado, ad esempio, di creare una Foo<int>::bar()funzione quando si utilizza il modello Foo per creare una classe Foo , la definizione effettiva di Foo<T>::bar()deve essere visibile.


E poiché è un modello per una classe , non è chiamata classe modello , ma modello di classe .
sbi

4
Il primo paragrafo è completamente corretto (e vorrei poter sottolineare "fuorviante"), ma non vedo la necessità del non sequitur in template.
Thomas Edleson

Alcuni compilatori lo useranno come suggerimento che la funzione può probabilmente essere inline, ma in effetti, non è garantito che sia inline solo perché lo dichiari inline(né il non dichiararlo inlinegarantisce che non sarà inline).
Keith M

4

So che questo è un vecchio thread ma ho pensato di doverlo menzionare la externparola chiave. Di recente ho riscontrato questo problema e l'ho risolto come segue

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

6
Questo in genere non comporterà l'inline della funzione a meno che non si utilizzi Whole Program Optimization (WPO).
Chuck Walbourn,

3

Perché il compilatore ha bisogno di vederli per inserirli in linea . E i file di intestazione sono i "componenti" che sono comunemente inclusi in altre unità di traduzione.

#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.

1

Funzioni inline

In C ++ una macro non è altro che una funzione inline. Così ora le macro sono sotto il controllo del compilatore.

  • Importante : se definiamo una funzione all'interno di una classe, questa diventerà automaticamente Inline

Il codice della funzione Inline viene sostituito nel punto in cui viene chiamato, quindi riduce il sovraccarico della funzione di chiamata.

In alcuni casi l'inlining della funzione non può funzionare, come

  • Se la variabile statica viene utilizzata all'interno della funzione inline.

  • Se la funzione è complicata.

  • Se la chiamata ricorsiva di funzione

  • Se l'indirizzo della funzione è preso implicitamente o esplicitamente

La funzione definita al di fuori della classe come di seguito può diventare inline

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

Anche la funzione definita all'interno della classe diventa inline

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

Qui entrambe le funzioni getSpeed ​​e setSpeed ​​diventeranno inline


eh, qualche bella informazione forse, ma in realtà non cerca di spiegare perché . Forse lo fai, ma semplicemente non lo stai chiarendo.
thecoshman

2
La seguente affermazione non è vera: "Importante: se definiamo una funzione all'interno di una classe, questa diventerà automaticamente Inline" Nemmeno se scrivi "inline" nella dichiarazione / definizione puoi essere certo che sia effettivamente inline. Nemmeno per i modelli. Forse intendevi che il compilatore assume automaticamente la parola chiave "inline", ma non deve seguirla e quello che ho notato è che nella maggior parte dei casi non integra tali definizioni nell'intestazione, nemmeno per semplici funzioni constexpr con aritmetica di base.
Pablo Ariel

Ehi, grazie per i commenti ... Di seguito sono riportate le righe di Thinking in C ++ micc.unifi.it/bertini/download/programmazione/… Pagina 400 .. Si prega di controllare .. Si prega di votare se siete d'accordo. Grazie ..... Inline all'interno delle classi Per definire una funzione inline, di solito devi precedere la definizione della funzione con la parola chiave inline. Tuttavia, questo non è necessario all'interno di una definizione di classe. Qualsiasi funzione definita all'interno di una definizione di classe è automaticamente inline.
Saurabh Raoot,

Gli autori di quel libro possono rivendicare ciò che vogliono, perché scrivono libri e non codice. Questo è qualcosa che ho dovuto analizzare in profondità per fare in modo che le mie demo 3D portatili si adattassero a meno di 64kb evitando il codice inline il più possibile. La programmazione riguarda i fatti e non la religione, quindi non importa se qualche "programmatore di dio" l'ha detto in un libro se non rappresenta ciò che accade nella pratica. E la maggior parte dei libri in C ++ là fuori contiene una raccolta di cattivi consigli, in cui di tanto in tanto riesci a trovare qualche trucco da aggiungere al tuo repertorio.
Pablo Ariel,

Ehi @PabloAriel Grazie ... Per favore analizza e fammi sapere .. Posso aggiornare questa risposta come da analisi
Saurabh Raoot,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.