Partager via


Fonctions Inline (C++)

Le mot clé inline 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 des fonctions inline permet accélérer l'exécution de votre programme, car celles-ci éliminent 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é par la mise en ligne de 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'inclut pas une fonction si son adresse est prise ou si le compilateur décide qu’elle est trop volumineuse pour.

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 constructeur Account est une fonction inline, car elle est définie dans le corps de la déclaration de classe. Les fonctions membres GetBalance, Deposit et Withdraw sont spécifiées inline dans leurs définitions. Le mot clé inline est facultatif 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 mot clé inline. Le mot clé inline peut être spécifié dans la déclaration de classe. Le résultat est identique.

Une fonction membre inline donnée doit être déclarée de la même manière dans chaque unité de compilation. Il doit exister une seule définition d'une fonction inline.

Une fonction membre de classe correspond par défaut à une liaison externe sauf qu'une définition de cette fonction contient le spécificateur inline. L’exemple précédent montre que vous n’avez pas besoin de déclarer ces fonctions explicitement avec le spécificateur inline. 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 spécificateurs inline et __inline indiquent au compilateur d'insérer une copie du corps de la fonction dans chaque emplacement où la fonction est appelée.

L'insertion appelée expansion inline ou incorporation se produit uniquement si l'analyse des coûts-bénéfice du compilateur montre qu'il vaut la peine de la faire. L'expansion inline minimise la surcharge des appels de fonction au coût potentiel d'une plus grande taille de code.

Le mot clé __forceinline substitue l'analyse des coûts-avantages et se base sur le jugement du programmeur à la place. Soyez prudent lorsque vous utilisez __forceinline. L'utilisation sans discernement de __forceinline peut entraîner un code plus volumineux avec seulement des gains de performance marginaux ou, dans certains cas, des pertes de performance (en raison de l’augmentation de la pagination d'un fichier exécutable plus volumineux).

Le compilateur traite les options d'expansion inline et les mots clés comme des suggestions. Rien ne garantit que les fonctions seront incorporées. Vous ne pouvez pas forcer le compilateur à incorporer une fonction particulière, même avec le mot clé __forceinline. Lorsque vous compilez avec /clr, le compilateur n'incorpore pas une fonction si des attributs de sécurité lui sont appliqués.

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 mot clé inline indique au compilateur que l'expansion inline est recommandé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 la mise en ligne, comme les autres, comme le détermine le compilateur. Ne dépendez pas du spécificateur inline pour provoquer la mise en ligne 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 /Ob pour influencer si l’extension 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 en utilisant le mot clé inline ou en plaçant la définition de la fonction dans la définition de la 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

Le mot clé __inline est équivalent à inline.

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

  • La fonction ou son appelant est compilé avec /Ob0 (option par défaut pour les versions debug).
  • 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, /O1 ou /O2.
  • La fonction est récursive et n’a pas #pragma inline_recursion(on) 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 de mise en ligne, utilisez le pragma inline_depth.
  • 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 modificateur naked __declspec.

Si le compilateur ne peut pas incorporer 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 mise en ligne 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 pragma inline_depth, 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 pragma inline_recursion contrôle l'expansion inline d'une fonction en cours d'expansion. Pour des informations connexes, consultez l'option du compilateur (/Ob) Expansion des fonctions inline.

FIN de la section spécifique à Microsoft

Pour plus d'informations sur l'utilisation du spécificateur inline, 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 longues passent proportionnellement moins de temps dans la séquence d'appel/retour et bénéficient moins de l'inlining.

Une classe Point 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;
}

Si la manipulation des coordonnées est une opération relativement courante dans un client de cette classe, la spécification des deux fonctions d’accesseur (x et y dans l’exemple précédent) comme inline stocke généralement la charge 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 installation du frame de pile
  • Communication de la valeur de retour
  • Restauration de l’ancien cadre de pile
  • Retour

Fonctions inline vs. macros

Une macro comporte certaines choses en commun avec une fonction inline. Toute fois, il existe deux différences majeures. 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 qui entraîne à #define mult2(a, b) (a) * (b), 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 fait que le deuxième appel de increment a retourné 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 vérifier si le comportement est prévu. Au lieu de cela, si la fonction inline square 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