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 GetBalance
membres , Deposit
et 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
,/O1
ou/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, utilisezinline_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 :
- Fonctions membres de classe 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 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 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#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
Commentaires
https://aka.ms/ContentUserFeedback.
Bientôt disponible : Tout au long de 2024, nous allons supprimer progressivement GitHub Issues comme mécanisme de commentaires pour le contenu et le remplacer par un nouveau système de commentaires. Pour plus d’informations, consultezEnvoyer et afficher des commentaires pour