Funzioni inline (C++)

La inline parola chiave suggerisce che il compilatore sostituisce il codice all'interno della definizione della funzione al posto di ogni chiamata a tale funzione.

In teoria, l'uso di funzioni inline può rendere il programma più veloce perché eliminano il sovraccarico associato alle chiamate di funzione. La chiamata a una funzione richiede il push dell'indirizzo restituito nello stack, il push degli argomenti nello stack, il passaggio al corpo della funzione e l'esecuzione di un'istruzione return al termine della funzione. Questo processo viene eliminato tramite l'inlining della funzione. Il compilatore offre anche diverse opportunità per ottimizzare le funzioni espanse inline rispetto a quelle che non lo sono. Un compromesso delle funzioni inline è che le dimensioni complessive del programma possono aumentare.

La sostituzione del codice inline viene eseguita a discrezione del compilatore. Ad esempio, il compilatore non inlinerà una funzione se viene preso il relativo indirizzo o se il compilatore decide che è troppo grande.

Una funzione definita nel corpo di una dichiarazione di classe è implicitamente una funzione inline.

Esempio

Nella dichiarazione di classe seguente il Account costruttore è una funzione inline perché è definita nel corpo della dichiarazione di classe. Le funzioni GetBalancemembro , Deposite Withdraw vengono specificate inline nelle relative definizioni. La inline parola chiave è facoltativa nelle dichiarazioni di funzione nella dichiarazione di 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;
}

Nota

Nella dichiarazione di classe le funzioni sono state dichiarate senza la inline parola chiave . La parola chiave può essere specificata nella dichiarazione di classe. Il inline risultato è lo stesso.

Una funzione membro inline specificata deve essere dichiarata allo stesso modo in ogni unità di compilazione. Deve essere presente esattamente una definizione di una funzione inline.

Per impostazione predefinita, una funzione membro della classe è un collegamento esterno a meno che una definizione per tale funzione non contenga l'identificatore inline . L'esempio precedente mostra che non è necessario dichiarare queste funzioni in modo esplicito con l'identificatore inline . L'uso inline nella definizione della funzione suggerisce al compilatore che deve essere considerato come una funzione inline. Tuttavia, non è possibile ripetere la richiesta di una funzione come inline dopo una chiamata a tale funzione.

inline, __inline e __forceinline

Gli inline identificatori e __inline suggeriscono al compilatore che inserisce una copia del corpo della funzione in ogni posizione chiamata dalla funzione.

L'inserimento, denominato espansione inline o inlining, si verifica solo se l'analisi dei vantaggi dei costi del compilatore indica che è utile. L'espansione inline riduce al minimo il sovraccarico delle chiamate di funzione a un costo potenziale di dimensioni maggiori del codice.

La __forceinline parola chiave esegue l'override dell'analisi dei costi-benefit e si basa invece sul giudizio del programmatore. Prestare attenzione quando si usa __forceinline. L'uso indiscriminato di __forceinline può comportare codice più grande con solo miglioramenti marginali delle prestazioni o, in alcuni casi, anche perdite di prestazioni (a causa dell'aumento del paging di un eseguibile più grande, ad esempio).

Il compilatore considera come suggerimenti le opzioni di espansione inline e le parole chiave. Non esiste alcuna garanzia che le funzioni verranno inlinede. Non è possibile forzare il compilatore a inline una determinata funzione, anche con la __forceinline parola chiave . Quando si esegue la compilazione con /clr, il compilatore non inlinerà una funzione se alla funzione sono applicati attributi di sicurezza.

Per la compatibilità con le versioni _inline precedenti e _forceinline sono sinonimi rispettivamente per __inline e __forceinline, a meno che non sia specificata l'opzione /Za del compilatore (Disabilita estensioni del linguaggio).

La inline parola chiave indica al compilatore che è preferibile l'espansione inline. Tuttavia, il compilatore può ignorarlo. Due casi in cui questo comportamento può verificarsi sono:

  • Funzioni ricorsive.
  • Funzioni a cui si fa riferimento tramite un puntatore in un punto diverso dell'unità di conversione.

Questi motivi possono interferire con l'inlining, come altri, come determinato dal compilatore. Non dipendere dall'identificatore inline per fare in modo che una funzione sia inlined.

Anziché espandere una funzione inline definita in un file di intestazione, il compilatore può crearlo come funzione chiamabile in più di un'unità di conversione. Il compilatore contrassegna la funzione generata per il linker per impedire violazioni ODR (One-Definition-Rule).

Come per le funzioni normali, non esiste un ordine definito per la valutazione degli argomenti in una funzione inline. In effetti, potrebbe essere diverso dall'ordine di valutazione dell'argomento quando viene passato usando il normale protocollo di chiamata di funzione.

Usare l'opzione /Ob di ottimizzazione del compilatore per determinare se l'espansione della funzione inline si verifica effettivamente.
/LTCG esegue l'inlining tra moduli, indipendentemente dal fatto che sia richiesto nel codice sorgente o meno.

Esempio 1

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

Le funzioni membro di una classe possono essere dichiarate inline, usando la inline parola chiave o inserendo la definizione della funzione all'interno della definizione della classe.

Esempio 2

// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>

class MyClass
{
public:
    void print() { std::cout << i; }   // Implicitly inline

private:
    int i;
};

Specifico di Microsoft

La __inline parola chiave è equivalente a inline.

Anche con __forceinline, il compilatore non può inline una funzione se:

  • La funzione o il chiamante viene compilata con /Ob0 (opzione predefinita per le compilazioni di debug).
  • La funzione e il chiamante utilizzano tipi diversi di gestione delle eccezioni (gestione delle eccezioni C++ per la prima, gestione delle eccezioni strutturata per il secondo).
  • La funzione ha un elenco di argomenti variabile.
  • La funzione usa l'assembly inline, a meno che non venga compilato con /Ox, /O1o /O2.
  • La funzione è ricorsiva e non è #pragma inline_recursion(on) impostata. Con la direttiva pragma, per le funzioni ricorsive viene eseguita l'espansione inline a una profondità predefinita di 16 chiamate. Per ridurre la profondità di inlining, usare inline_depth pragma.
  • La funzione è virtuale e viene chiamata virtualmente. Per chiamate dirette alle funzioni virtuali può essere eseguita l'espansione inline.
  • Il programma utilizza l'indirizzo della funzione e la chiamata viene effettuata tramite il puntatore alla funzione stessa. Per chiamate dirette alle funzioni di cui ne avevano l'indirizzo può essere eseguita l'espansione inline.
  • La funzione è contrassegnata anche con il naked__declspec modificatore.

Se il compilatore non può inline una funzione dichiarata con __forceinline, genera un avviso di livello 1, tranne quando:

  • La funzione viene compilata usando /Od o /Ob0. In questi casi non è previsto alcun inlining.
  • La funzione viene definita esternamente, in una libreria inclusa o in un'altra unità di conversione oppure è una destinazione di chiamata virtuale o una destinazione di chiamata indiretta. Il compilatore non è in grado di identificare il codice non inlined non trovato nell'unità di conversione corrente.

Le funzioni ricorsive possono essere sostituite con codice inline a una profondità specificata dal inline_depth pragma, fino a un massimo di 16 chiamate. Dopo tale profondità, le chiamate di funzioni ricorsive vengono considerate come chiamate a un'istanza della funzione. La profondità a cui le funzioni ricorsive vengono esaminate dall'euristica inline non può superare 16. Il inline_recursion pragma controlla l'espansione inline di una funzione attualmente in fase di espansione. Per informazioni correlate, vedere l'opzione del compilatore Inline-Function Expansion (/Ob).

Fine sezione specifica Microsoft

Per altre informazioni sull'uso dell'identificatore inline , vedere:

Quando usare le funzioni inline

Le funzioni inline vengono usate in modo ottimale per funzioni di piccole dimensioni, ad esempio quelle che forniscono l'accesso ai membri dati. Le funzioni brevi sono sensibili al sovraccarico delle chiamate di funzione. Le funzioni più lunghe impiegano proporzionalmente meno tempo nella sequenza chiamante e restituiscono e traggono meno vantaggio dall'inlining.

Una Point classe può essere definita come segue:

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

Supponendo che la manipolazione delle coordinate sia un'operazione relativamente comune in un client di tale classe, specificando le due funzioni di accesso (x e y nell'esempio precedente), in inline modo da risparmiare in genere il sovraccarico su:

  • Chiamate di funzione (incluso il passaggio dei parametri e il posizionamento dell'indirizzo dell'oggetto nello stack)
  • Conservazione dello stack frame del chiamante
  • Configurazione di un nuovo stack frame
  • Comunicazione del valore restituito
  • Ripristino del frame dello stack precedente
  • Restituzione

Funzioni inline e macro

Una macro presenta alcuni aspetti in comune con una inline funzione. Ma ci sono differenze importanti. Si consideri l'esempio seguente:

#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

Ecco alcune delle differenze tra la macro e la funzione inline:

  • Le macro vengono sempre espanse inline. Tuttavia, una funzione inline è inlined solo quando il compilatore determina che è la cosa ottimale da eseguire.
  • La macro può comportare un comportamento imprevisto, che può causare bug sottili. Ad esempio, l'espressione mult1(2 + 2, 3 + 3) si espande a 2 + 2 * 3 + 3 cui restituisce 11, ma il risultato previsto è 24. Una correzione apparentemente valida consiste nell'aggiungere parentesi intorno a entrambi gli argomenti della macro di funzione, con conseguente #define mult2(a, b) (a) * (b), che risolverà il problema a portata di mano, ma può comunque causare un comportamento sorprendente quando parte di un'espressione più grande. Ciò è stato illustrato nell'esempio precedente e il problema potrebbe essere risolto definendo la macro come tale #define mult3(a, b) ((a) * (b)).
  • Una funzione inline è soggetta all'elaborazione semantica da parte del compilatore, mentre il preprocessore espande le macro senza lo stesso vantaggio. Le macro non sono indipendenti dai tipi, mentre le funzioni sono.
  • Le espressioni passate come argomenti alle funzioni inline sono valutate una sola volta. In alcuni casi, le espressioni passate come argomenti alle macro possono essere valutate più volte. Si consideri, ad esempio, quanto segue:
#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

In questo esempio, la funzione increment viene chiamata due volte quando l'espressione sqr(increment(c)) si espande fino a ((increment(c)) * (increment(c))). In questo modo la seconda chiamata di increment restituisce 6, quindi l'espressione restituisce 30. Qualsiasi espressione contenente effetti collaterali può influire sul risultato quando viene utilizzata in una macro, esaminare la macro completamente espansa per verificare se il comportamento è previsto. Se invece è stata usata la funzione square inline, la funzione increment verrà chiamata una sola volta e verrà ottenuto il risultato corretto di 25.

Vedi anche

noinline
auto_inline