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 pragmainline_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 :
- Fonctions membres des classes inline
- Définition de fonctions C++ incorporées avec dllexport et dllimport
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 sur2 + 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.