Fonctions inline (C++)

La inline mot clé suggère que le compilateur remplace le code dans la définition de fonction à la place de chaque appel à cette fonction.

En théorie, l’utilisation de fonctions inline peut accélérer votre programme, car elle élimine la surcharge associée aux appels de fonction. L’appel d’une fonction nécessite d’envoyer (push) l’adresse de retour sur la pile, d’envoyer (push) des arguments sur la pile, de passer au corps de la fonction, puis d’exécuter une instruction de retour une fois la fonction terminée. Ce processus est éliminé en inlinant la fonction. Le compilateur a également différentes opportunités d’optimiser les fonctions développées inline par rapport à celles qui ne le sont pas. Un compromis entre les fonctions inline est que la taille globale de votre programme peut augmenter.

La substitution de code inline est effectuée à la discrétion du compilateur. Par exemple, le compilateur n’inline pas une fonction si son adresse est prise ou si le compilateur décide qu’il est trop volumineux.

Une fonction définie dans le corps d’une déclaration de classe est implicitement une fonction inline.

Exemple

Dans la déclaration de classe suivante, le Account constructeur est une fonction inline, car elle est définie dans le corps de la déclaration de classe. Les fonctions GetBalancemembres , Depositet Withdraw sont spécifiées inline dans leurs définitions. La inline mot clé est facultative dans les déclarations de fonction dans la déclaration 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;
}

Remarque

Dans la déclaration de classe, les fonctions ont été déclarées sans la inline mot clé. La inline mot clé peut être spécifiée dans la déclaration de classe ; le résultat est le même.

Une fonction membre inline donnée doit être déclarée de la même manière dans chaque unité de compilation. Il doit y avoir exactement une définition d’une fonction inline.

Une fonction membre de classe est définie par défaut sur une liaison externe, sauf si une définition pour cette fonction contient le inline spécificateur. L’exemple précédent montre que vous n’avez pas besoin de déclarer ces fonctions explicitement avec le inline spécificateur. L’utilisation inline dans la définition de fonction suggère au compilateur qu’il soit traité comme une fonction inline. Toutefois, vous ne pouvez pas redéclarer une fonction comme inline après un appel à cette fonction.

inline, __inline et __forceinline

Les inline spécificateurs __inline suggèrent au compilateur qu’il insère une copie du corps de la fonction à chaque endroit où la fonction est appelée.

L’insertion, appelée expansion inline ou inlining, se produit uniquement si l’analyse des coûts du compilateur montre qu’elle vaut la peine d’être effectuée. L’extension inline réduit la surcharge des appels de fonction au coût potentiel d’une plus grande taille de code.

Le __forceinline mot clé remplace l’analyse des coûts-avantages et s’appuie sur le jugement du programmeur à la place. Faites preuve de prudence lors de l’utilisation __forceinline. L’utilisation aveugle de __forceinline code peut entraîner un code plus volumineux avec seulement des gains de performances marginaux ou, dans certains cas, même des pertes de performances (en raison de l’augmentation de la pagination d’un fichier exécutable plus volumineux, par exemple).

Le compilateur traite les options d'expansion inline et les mots clés comme des suggestions. Il n’y a aucune garantie que les fonctions seront inline. Vous ne pouvez pas forcer le compilateur à inliner une fonction particulière, même avec le __forceinline mot clé. Lorsque vous compilez avec /clr, le compilateur n’inline pas une fonction s’il existe des attributs de sécurité appliqués à la fonction.

Pour la compatibilité avec les versions précédentes, _inline et _forceinline sont synonymes respectivement de __inline et __forceinline, sauf si l’option du compilateur /Za (Désactiver les extensions de langage) est spécifiée.

Le inline mot clé indique au compilateur que l’extension inline est préférée. Toutefois, le compilateur peut l’ignorer. Deux cas où ce comportement peut se produire sont les suivants :

  • Fonctions récursives.
  • Fonctions référencées par l'intermédiaire d'un pointeur ailleurs dans l'unité de traduction.

Ces raisons peuvent interférer avec l’inlination, comme les autres, comme le détermine le compilateur. Ne dépendez pas du inline spécificateur pour provoquer l’inline d’une fonction.

Au lieu de développer une fonction inline définie dans un fichier d’en-tête, le compilateur peut le créer en tant que fonction pouvant être appelée dans plusieurs unités de traduction. Le compilateur marque la fonction générée pour l’éditeur de liens afin d’empêcher les violations odR (one-definition-rule).

Comme pour les fonctions normales, il n’existe aucun ordre défini pour l’évaluation des arguments dans une fonction inline. En fait, il peut être différent de l’ordre d’évaluation de l’argument lorsqu’il est passé à l’aide du protocole d’appel de fonction normal.

Utilisez l’option d’optimisation du compilateur pour influencer si l’extension /Ob de fonction inline se produit réellement.
/LTCG effectue l’incorporation entre modules, qu’il soit demandé dans le code source ou non.

Exemple 1

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

Les fonctions membres d’une classe peuvent être déclarées inline, soit à l’aide de l’mot clé inline , soit en plaçant la définition de fonction dans la définition de classe.

Exemple 2

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

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

private:
    int i;
};

Spécifique à Microsoft

La __inline mot clé équivaut à inline.

Même avec __forceinline, le compilateur ne peut pas inliner une fonction si :

  • La fonction ou son appelant est compilée avec /Ob0 (l’option par défaut pour les builds de débogage).
  • La fonction et l'appelant utilisent différents types de gestion des exceptions (gestion des exceptions C++ dans l'un, gestion structurée des exceptions dans l'autre).
  • La fonction a une liste d'arguments variable.
  • La fonction utilise l’assembly inline, sauf si elle est compilée avec /Ox, /O1ou /O2.
  • La fonction est récursive et n’a #pragma inline_recursion(on) pas défini. Avec le pragma, les fonctions récursives sont incorporées à une profondeur par défaut de 16 appels. Pour réduire la profondeur d’inlining, utilisez inline_depth pragma.
  • La fonction est virtuelle et est appelée virtuellement. Les appels directs aux fonctions virtuelles peuvent être incorporés.
  • Le programme prend l'adresse de la fonction et l'appel est effectué via le pointeur vers la fonction. Les appels directs aux fonctions dont l'adresse est acceptée peuvent être incorporés.
  • La fonction est également marquée avec le naked__declspec modificateur.

Si le compilateur ne peut pas inliner une fonction déclarée avec __forceinline, il génère un avertissement de niveau 1, sauf quand :

  • La fonction est compilée à l’aide de /Od ou /Ob0. Aucune inlining n’est attendue dans ces cas.
  • La fonction est définie en externe, dans une bibliothèque incluse ou une autre unité de traduction, ou est une cible d’appel virtuel ou une cible d’appel indirecte. Le compilateur ne peut pas identifier le code non inclus qu’il ne trouve pas dans l’unité de traduction actuelle.

Les fonctions récursives peuvent être remplacées par du code inline à une profondeur spécifiée par le inline_depth pragma, jusqu’à un maximum de 16 appels. Au-delà de cette profondeur, les appels de fonction récursive sont traités comme des appels à une instance de la fonction. La profondeur à laquelle les fonctions récursives sont examinées par l’heuristique inline ne peut pas dépasser 16. Le inline_recursion pragma contrôle l’expansion inline d’une fonction actuellement en cours d’expansion. Pour plus d’informations, consultez l’option du compilateur Inline-Function Expansion (/Ob).

FIN de la section spécifique à Microsoft

Pour plus d’informations sur l’utilisation du inline spécificateur, consultez :

Quand utiliser les fonctions inline

Les fonctions inline sont mieux utilisées pour les petites fonctions, telles que celles qui fournissent l’accès aux membres de données. Les fonctions courtes sont sensibles à la surcharge des appels de fonction. Les fonctions plus longues passent proportionnellement moins de temps dans l’appel et la séquence de retour et bénéficient moins de l’inlining.

Une Point classe peut être définie comme suit :

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

En supposant que la manipulation de coordonnées est une opération relativement courante dans un client d’une telle classe, en spécifiant les deux fonctions d’accesseur (x et y dans l’exemple précédent), car inline elle enregistre généralement la surcharge sur :

  • Appels de fonction (notamment le paramètre passe et place l'adresse de l'objet dans la pile)
  • Conservation du frame de pile de l'appelant
  • Nouvelle configuration de trame de pile
  • Communication de la valeur de retour
  • Restauration de l’ancien cadre de pile
  • Retour

Fonctions inline et macros

Une macro comporte certaines choses en commun avec une inline fonction. Mais il y a des différences importantes. Prenons l’exemple suivant :

#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

Voici quelques-unes des différences entre la macro et la fonction inline :

  • Les macros sont toujours développées inline. Toutefois, une fonction inline n’est incluse que lorsque le compilateur détermine qu’il s’agit de la chose optimale à faire.
  • La macro peut entraîner un comportement inattendu, ce qui peut entraîner des bogues subtils. Par exemple, l’expression mult1(2 + 2, 3 + 3) s’étend sur 2 + 2 * 3 + 3 laquelle la valeur est 11, mais le résultat attendu est 24. Un correctif apparemment valide consiste à ajouter des parenthèses autour des deux arguments de la macro de fonction, ce #define mult2(a, b) (a) * (b)qui entraîne , ce qui résout le problème à la main, mais peut encore provoquer un comportement surprenant lorsqu’une partie d’une expression plus grande. Cela a été démontré dans l’exemple précédent, et le problème peut être résolu en définissant la macro comme telle #define mult3(a, b) ((a) * (b)).
  • Une fonction inline est soumise au traitement sémantique par le compilateur, tandis que le préprocesseur développe des macros sans ce même avantage. Les macros ne sont pas de type sécurisé, tandis que les fonctions sont.
  • Les expressions passées comme arguments aux fonctions inline sont évaluées une fois. Dans certains cas, les expressions passées comme arguments aux macros peuvent être évaluées plusieurs fois. Prenons l’exemple suivant :
#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

Dans cet exemple, la fonction increment est appelée deux fois au fur et à mesure que l’expression sqr(increment(c)) se développe sur ((increment(c)) * (increment(c))). Cela a provoqué le deuxième appel de increment retourner 6, d’où l’expression est évaluée à 30. Toute expression qui contient des effets secondaires peut affecter le résultat lorsqu’elle est utilisée dans une macro, examinez la macro entièrement développée pour case activée si le comportement est prévu. Au lieu de cela, si la fonction square inline a été utilisée, la fonction increment ne serait appelée qu’une seule fois et le résultat correct de 25 sera obtenu.

Voir aussi

noinline
auto_inline