Oui, on peut dire ça comme ça, puisqu'un accesseur, ça peut s'inliner dans chaque unité de compilation sans rien dire au compilateur.
Version imprimable
Seulement s'il est défini dans le header, et dans ce cas:
- soit il est défini hors de la classe, et explicitement déclaré inline pour avoir un linkage faible
- soit il est défini dans la classe et donc (si je me souviens bien) implicitement inline.
Note: Je suppose la LTCG désactivée dans le cas présent. De toute façon, elle ne fait que repousser les frontières, pas les supprimer complètement.
- S'il est défini en-dehors de la classe, ce n'est pas un accesseur (ou il est friend, mais il devrait être dans la classe)
- Oui, il est implicitement inline, c'est le but
LTCG, ça ne marche pas encore, et ça sera toujours à la traîne par rapport à l'utilisation correct du langage (encore testé récemment sur le compilateur Intel qui plantait lamentablement sur un code qu'il passe sans pb sans).
Note: par "défini hors de la classe", je voulais juste dire ceci:
Déclaré dans la classe, défini en dehors (mais ici, inline et dans le header, ce qui est la même chose que le définir dans la classe).Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 //UneClasse.h class UneClasse { private: int x; public: void CalculerX(int toto, int tata, int titi); int GetX() const; }; inline int UneClasse::GetX() const { return x; }
ah, OK ;)
J'ai pour principe d'écrire le code dans la classe si c'est pour un en-tête. C'est plus ou moins ce qui est proposé dans Clean Code.
Ce n'est pas toujours possible pour des histoires d'inter-dépendances.Citation:
J'ai pour principe d'écrire le code dans la classe si c'est pour un en-tête.
(T::foo utilise un U, mais U a besoin que T soit non-abstrait)
Dans la dernière boite où j'ai bossé, il y avait le même genre de classe Vector mathématique templaté où la plupart des operateurs et accesseurs étaient inline.Citation:
Bon, aujourd'hui j'ai passé un autre entretien où j'ai mis le nez dans le code de leur logiciel etc... et en voyant un header de classe chargée d'effectuer des operations mathématiques (du type vecteurs etc...), y'avait du inline partout, et quelque part, j'ai trouvé ça... rassurant. En fait, je pense qu'au niveau technique l'utilité de ce mot-clé est réellement contestable (je suis d'accord avec Matthieu au sujet du problème de template, pour moi cette utilisation de inline est une pirouette qui sert à masquer un autre problème en fait).
En fait je me suis demandé si c'était vraiment important alors j'ai enlevé les inline pour voir.
La conséquence était qu'en debug il y avait a la fois une perte de vitesse visible et une augmentation de la taille de l'executable (sur NDS, donc ça posait problème) - la classe étant beaucoup utilisé (jeu vidéo oblige).
En "release" il n'y avait pas de différence parceque le compilateur (CodeWarrior) était configuré pour faire de l'inlining agressif dans ce mode.
Dans ce cas l'inlining avait un interet essentiel.
Dans mes projets actuels je n'utilise pas inline, je fais confiance au compilateur (essentiellement VC), suivant ce que j'ai lu ici et dans des tas de bouquins. Je n'ai toutefois pas les même contraintes (pc).
En revanche comme dit auparavant par d'autres je pense que je prendrais en compte l'inlining si j'ai besoin de faire de l'optimization (mais après d'autres techniques et mesures) mais aussi si j'ai besoin de rendre l'application plus rapide en debug. Visiblement inline permet d'aider le compilateur a inliner en debug, ce qui aide énormément dans le domaine du jeu vidéo (où dans pas mal de type de jeux on se retrouve a debugger exclusivement en Release ou equivalent parceque sinon le tout est tout simplement trop lent).
Par contre je me méfie quand même quand je vois du code des autres où il y a des fonctions inline.
Je ne m'avancerai pas là dessus. Je n'ai pas (encore) vérifié avec VC9. Je n'utilise pas gcc. En revanche je sais que par exemple CodeWarrior le fait () et que c'est le compilateur "fourni de base" pour pas mal de consoles (Wii, GBA/NDS, PS2 il me semble). Le domaine du jeu vidéo sur console n'est certainement pas le plus commun dans le developpement C++ mais il est tout de même important j'imagine.Citation:
Pour 99% des compilateurs, il est possible de lui dire d'inliner indépendamment du mode debug ou non. Donc me^me dans ce mode, je ne vois pas l'intérêt du mot clé.
Autrement dit ton affirmation est poil abusive a mon gout; on va dire que la plupart des compilateurs ne font certainement pas la différence. Ca reste à prouver. (Mais c'est vrai que généralement je pars du principe que ça ne fais pas de différence, même si dans certains cas c'est faux).
Ce qui me fais penser... qu'un document rassemblant et comparant les comportements des différents compilateurs communs avec inline serait interessant et constructif pour cette discussion.
Edit> J'avais mal compris ta phrase : en fait dans l'exemple dont je parlais il y a bien une différence c'est qu'avec le mot clé cela permet de préciser qu'une partie uniquement du code est inline et non tout le projet contrairement aux configurations d'inlining qui s'appliquent a tout le projet. Dans l'exemple réel que je donne, seul la classe en question était inliné en debug et comme expliqué, ça avait un impact crucial (sans ça le jeu ne pouvait tout simplement pas tourner correctement en debug).
Bon bon bon,
Ma question de départ portait sur le mot-clé inline uniquement pour l'inlining. Mais C++ a cette particularité que pas mal de mot-clés ont des utilisations au delà de ce leur utilité "initiale". Bref.
J'ai fait un autre entretien hyper technique avec 2 team leader d'un super gros projet C++ (350 développeurs rien qu'en France...), et là, tadaaaaaa : on m'a sorti l'argument qui fait que inline est toujours d'actualité même dans le cas de l'optimisation de getters : les DLLs. Et oui, quand il s'agit d'appeler une fonction exportée par une DLL, le compilateur ne peut pas l'inliner si sa définition n'est pas disponible dans le header... Donc, en bref, inline ne sert plus à rien pour vaincre les barrières d'une unité de compilation, mais il est toujours nécessaire pour dépasser les frontières d'un module utilisé dynamiquement.
C'est le problème des tests par écrit : on ne peut pas discuter avec l'auteur et savoir ce qu'il veut entendre / son niveau. Donc j'ai dit qu'il y avait un paradoxe à mixer virtual et inline (indirection obligatoire via la vtable)... tout en précisant que le compilateur pouvait dans certaines conditions outrepasser la vtable et effectuer un appel direct, et que donc dans ce cas un inlining était possible. Mais que même dans ce cas, le mot-clé inline en lui-même etait superflu du fait que le compilo sait faire tout seul l'optimisation jusqu'au bout.
En recherchant sur le net, j'ai trouvé cet article :
http://msdn.microsoft.com/en-us/magazine/cc301407.aspx
et l'auteur mentionne le fait que sur certains (vieux ?) compilateurs on peut éventuellement se retrouver avec plusieurs vtables pour un même objet si les fonctions virtuelles sont inlinées, bien que le standard C++ interdise cela.
Puisqu'on va loin dans cette discussion, continuons avec les risques de fournir l'implémentation dans un header. Le compilo se fiche d'avoir plusieurs définitions, ok. Mais sur un gros projet, il est relativement facile de générer plusieurs définitions... différentes. Et alors...
Un jour, le soft que je développais fonctionnait sans problème en debug et plantait en release avec l'optimisation WPO (LTCG). J'ai mis 2 jours à localiser le problème : je n'avais pas les mêmes flags de compilation dans tous les modules. Conséquences : le code des templates n'était pas compilé de la même manière dans tous les modules (c'était lié aux trucs de "sécurité" de la STL de VC++ : flag _SECURE_SCL etc...). Ce non respect du standard C++ (multiples définitions différentes d'un même code template) ne se manifestait qu'avec l'optimisation sur l'ensemble du programme.
Il existe aussi les fonctions intrinsèques :
http://msdn.microsoft.com/en-us/libr...43(VS.80).aspx
A l'exception de leur appel depuis un autre module comme une dll, comme expliqué plus haut.
Mouais... Je ne suis pas hyper convaincu. Pour la bonne raison que si les perfs comptent de manière si sensible que l'inlinig soit rentable, autant supprimer la DLL et faire du link statique, on aura les mêmes avantages pour toutes les fonctions, pas seulement les getters (qui d'ailleurs s'ils sont trop monbreux et ont un impact sur les perfs sont probablement signe de mauvais design). J'ai déjà gagné 10% de perf sur du code déjà assez optimisé simplement en linkant statiquement ce qui avant était une DLL.
Ca ne me surprend pas, ça a aussi généralement un intérêt positif au niveau de la taille du code (élimination du code mort...), sans parler du temps d'initialisation plus rapide de l'appli (on économise toute l'édition de liens dynamique).
On a parlé rapidement de pourquoi ils utilisaient des dlls. Au delà du fait qu'ils utilisent beaucoup COM, ils m'ont dit qu'ils sont aussi obligés pour des raisons de redistribution / de mises à jours (ils ont 255 composants assemblés / vendus à la demande du client). Ils m'ont mentionné le gain de temps d'un point de vue recompilation (cela dit c'est un argument valable pour le développement / en debug, mais beaucoup moins pour builder une version finale). D'ailleurs, c'est un effet de bord des fonctions inline à ce niveau : en changer l'implémentation force à recompiler non seulement la DLL mais aussi toutes celles qui l'utilise. C'est ce qu'ils avaient à reprocher aux fonctions inline.
Toujours pas compris le coup du chargement dynamique. Ce qu'ils voulaient dire, c'est qu'inline pouvait les empêcher d'utiliser la bibliothèque parce que la fonction n'était pas exportée ? De toute manière, l'accesseur ne peut jamais être inliné puisqu'on n'a pas le code correspondant :koi:
Quand une fonction est mise en inline dans le .h d'une DLL, elle pourra être effectivement inlinée dans le code de la DLL, mais aussi dans le code appelant. En pratique, ça revient à définir ce code 2 fois, dans la DLL et dans son client.
C'est moi qui dit ça. Pour chaque DLL liée à ton exe, le loader de l'OS doit déjà localiser le fichier sur le disque puis le charger en mémoire (ce qui peut provoquer un conflit du fait qu'une autre DLL occupe déjà la plage d'adresse de "préférence" de ladite dll, ce qui entraine un mécanisme couteux de relocation -> le rebasing), mais aussi effectue la liaison dynamique entre tous les symboles importées (sous forme de chaînes de caractères souvent... donc potentiellement des milliers de strings à comparer / matcher). Si on utilise plusieurs grosses DLL, ça finit par se ressentir même de nos jours. Mais si on link statiquement, on économise tout cela (ou plutôt on le paye une seule fois au moment du link).
Non non, ils utilisent inline. L'effet de bord, c'est que si tu modifies l'implémentation d'une fonction inline, tu dois recompiler tout le code client qui inclut ce header (normal). Sur un projet de plusieurs millions de lignes de codes, ça les agace un peu (normalement recompiler la DLL suffit).
Ah, OK... Je comprend mieux ;) (ou plutôt j'avais rien compris de la problématique qu'ils avaient ;))
Puisqu'on parle des conséquences avancées de l'utilisation de inline notamment avec les DLLs, je viens de lire ceci :
http://techbase.kde.org/Policies/Bin...ine_a_function
En clair, il ne faut pas inliner une fonction exportée si vous souhaiter conserver une compatibilité binaire avec votre dll : vue qu'elle aura été inlinée dans le code des DLLs utilisatrices, le changement de son implémentation dans la DLL passera inaperçu / sera ignoré.
Donc si on modifie une fonction inline exportée, non seulement il faut recompiler toutes les DLLs utilisatrices, mais aussi les redistribuer chez le client même si leur code source n'a pas été modifié. A utiliser avec modération donc vu que la modification d'une seule ligne de code dans une fonction inline peut nécessiter un patch chez le client de plusieurs dizaines / centaines de DLLs.
Ça, il suffit de faire du prelinking, pas besoin de lier statiquement.Citation:
sans parler du temps d'initialisation plus rapide de l'appli (on économise toute l'édition de liens dynamique).
Serais-je le seul à trouver un illogisme flagrant dans le seul fait d'inliner une fonction exportée d'une dll, ne serait-ce que parce que l'inlining d'un fonction a pour résultat de remplacer l'appel de la dite fonction par le code binaire de son implémentation :question:
San même prendre en compte les restriction, et bien que ce soient deux points de vues tout à fait orthogonaux, les objectifs de l'un et de l'autre me semblent très clairement incompatibles, pas vous :question:
Disons que ça n'a vraiment d'intéret que lorsqu'on fait une DLL avec une interface C++: L'implémentation "lourde" de la classe peut être dans la DLL, et les accesseurs peuvent être inlinés...
Je reconnais être parfois buté, mais, pour moi, qu'il s'agisse de fonctions libres ou de fonctions membres ne change pas grand chose...
Le but premier d'une dll est, pour faire simple, de s'assurer que nous pourrons utiliser une bibliothèque sans tenir compte du compilateur (ou de sa version) ni des options de compilation utilisées pour l'obtenir.
Si l'on décide de permettre au compilateur de remplacer l'appel d'une fonction par le binaire correspondant à son implémentation, il faut se dire que ce sera le binaire produit par ... le compilateur de l'utilisateur, ce qui nous ramène aux restrictions de cet usage et qui est, finalement, tout à fait incompatible avec le but premier des dll's.
A ce rythme, autant garder une bibliothèque statique ou, si l'on considère (mais ce n'est jamais qu'une extension due aux capacités propres des dll's) la dll comme le moyen de créer des plugins, autant que les fonctions ne soient pas inlinée, pour justement rester en accord avec le principe même de la dll ;)
Déjà, l'indépendance au compilateur d'une DLL n'est valable que si on exporte uniquement des fonctions C, ou sous windows si on fait une DLL COM. C'est assez limité.
Mais surtout, pour certaines personnes, le but d'une DLL n'est pas ça. Dans une application multi-exécutable, le but d'une DLL peut être de diminuer la taille du code à livrer. Il peut aussi être de permettre la livraison de patchs par mail, même si l'application en elle même est trop grosse pour être livrée ainsi. Il peut enfin être de diminuer les temps de recompilation(*) pour une équipe de dev. Dans ces trois cas, avoir quelques fonctions inlines n'est pas absurde.
(*) C'est l'argument principal là où je travaille (et la situation prédate mon arrivée, et globalement je la désapprouve).
Il y a en effet un cas où ça accélère le développement, c'est si on change l'implémentation d'un module sans en changer l'interface, on n'a pas besoin de relinker tous les exécutables qui utilisent ce module pour qu'ils bénéficient de la mise à jour.
Par contre, si on modifie l'interface, il faut dans les deux cas tout recompiler au final. Mais le développeur qui a modifié l'interface l'a probablement fait dans l'optique d'un exécutable spécifique, et s'il travaille en statique, il n'a à recompiler que cet exécutable là pour tester, la recompilation des autres ayant lieu en asynchrone la nuit, alors que s'il travaille en dynamique, il doit tout recompiler pour effectuer même ses premiers tests.
Le gain de temps pour le développement de l'utilisation de DLL n'est donc pas forcément évident. Mais de toute façon, je ne pense pas que ce soit le genre de considération qui devrait le plus impacter le choix d'architecture, mais plus les contraintes de déploiement ou de performances, et dans ces deux cas, à part une situation de type plug-in, les DLL ajoutent un niveau de complexité certain.
Ce que je veux dire, c'est que la fonction inline est exportée parce qu'elle est membre d'une classe qui l'est. Quand on exporte une classe, on est déjà compiler specific.
C'est une pratique très courante, il suffit de regarder QObject dans Qt :
Ils sont très prudents. Ils inlinent pas objectName() ou blockSignals(), mais des getters qui ne changeront à priori jamais. Et ainsi ils arrivent à conserver une très bonne compatibilité binaire. C'est une utilisation réfléchie et modérée de inline.Code:
1
2
3
4
5
6
7 QString objectName() const; void setObjectName(const QString &name); inline bool isWidgetType() const { return d_ptr->isWidget; } inline bool signalsBlocked() const { return d_ptr->blockSig; } bool blockSignals(bool b);
Certaines applis ont besoin d'utiliser des DLLs, pour la simple raison que tu ne peux pas vraiment avoir une moitié de tes libs sous forme statique et l'autre sous forme de DLLs. A partir du moment où du code est partagé entre les DLLs "obligatoires" (plugin) et ton exe, ce code doit être partagé sous forme de DLL. Et en général, ce code en commun à toute l'appli est utilisé de manière intensive, donc se pose le problème d'optimiser... et nous y voilà.
Je ne suis moi aussi pas convaincu par l'aspect temps de recompilation, dans la mesure où lors du développement ok c'est vrai, mais pour le compilation d'une release finale on s'en fiche un peu vu que c'est pas toutes les 10 minutes qu'on fait ça.
Il y a aussi d'autres arguments en faveur des DLL, en particulier sur les (très) gros projets. Qt par exemple : la compilation en static de certaines bibliothèques (WebKit) échoue tout simplement si elle n'est pas impossible (Phonon). Cela échoue en général à l'édition des liens :
http://labs.trolltech.com/blogs/2009...ng-convention/Citation:
I spent some time trying to get QtWebKit to compile on Solaris / UltraSPARC / Sun Studio (CC 5.9), as well as AIX / POWER6 / xlC 7.0. Long story short: it compiles on both, with a large set of patches; it doesn’t link on AIX because the Out-Of-Memory killer kills the linker.
En ce qui me concerne sous Windows, avec l'option WPO, le linker explose sur WebKit quand le fichier lib qu'il génère commence à dépasser les 2Go...
Donc sur un projets de plusieurs millions de lignes de code, lier statiquement peut être un véritable problème. Toujours dans cette boîte, ils m'ont dit que rien que pour certaines DLLs ou Exe, le linker montait à 600Mo d'occupation mémoire, sachant qu'il y a 255 DLLs...
Un autre aspect est l'aspect modularité. Leur logiciel tourne sur 120.000 postes clients... et est configuré sur mesure sur chaque poste... compiler 120.000 versions customisées est impensable, passer par des DLLs est obligatoire. Toutes les grosses applis de ce genre (le client choisit les modules qu'il veut parmi des centaines / milliers disponibles) passent par des DLLs il me semble.
En réfléchissant à tout ça, je me pose la question suivante : dans le cas d'un très gros projet découpé en modules qui peuvent être compilés aussi bien sous forme de libs statiques que de dlls, si jamais le projet est trop gros pour être compilé avec une optimisation globale sur l'ensemble du source (WPO), je me demande d'un point de vue performance ce qui sera le plus intéressant :
- compiler et assembler en même temps toutes les libs sous forme statique mais sans optimisation globale sur l'ensemble du programme
- compiler séparément chaque module sous forme de DLL mais avec optimisation globale (au niveau de chaque DLL)
Je pense en particulier à une bibliothèque telle que VTK qui contient dans chaque module des algorithmes très couteux. Il s'agit en gros de savoir ce qui procure la meilleure optimisation :
- une optimisation poussée spécifique à chaque module (DLL WPO)
- une optimisation "normale" mais qui couvre l'ensemble des modules (lib statiques)
De toute manière, il ne faut pas reposer sur WPO pour avoir de la perf. Les backends des compilateurs ne sont pas suffisamment performants pour gérer cela. Et même si c'était le cas, ils seront toujours moins performants que le compilateur lui-même. Il est nécessaire de restructurer le code dans ce cas. On a eu le souci, on a modifié la structure, et ça marche. Si le code critique réside dans une unité de compilation, pas la peine d'avoir WPO.
Après, DLL ou bib statique ? DLL, c'est mieux, moins de risque de perdre des fonctionalités (on est sûr que tous les objets seront initialisés, par exemple un registre initialisé par des objets globaux ne fonctionnera peut-être plus avec une bibliothèque statique.
Hum je crois que ça part un peu en hors sujet mais je voulais juste ajouter qu'il y a quelques mois j'ai découpé l'executable du jeu sur lequel je travaille chez moi en plusieurs dll liés par un exe pour d'autres raisons que celles citées ici (en plus du temps de link qui effectivement est largement réduit pendant le développement, et ça aide):
- je voulais que certains modules de l'application exposent des fonctions et classes pour permettre à l'utilisateur d'ajouter du contenu;
- je voulais que certaines parties soient liés à d'autres dlls de bibliothèques tièrces tout en faisant en sorte que les autres parties soient "indépendantes" - le compilo/linker aidant alors à empecher du code d'une dll censée indépendante à avoir du code appelant les dll tièrces; cela est purement pratique c'est vrai, mais j'essaie de faire en sorte que le compilo/linker m'aide aa maintenir mes besoins sur le projet;
j'ai essayé de lire une partie de la discussion car je tente de comprendre ce que fait inline.
Est-il possible de voir ce qu'il se passe au niveau du code quand on déclare une fonction avec inline?
J'ai tenté d'utiliser le Disassembly (j'ai VC2010), pour voir ce que cela faisait au niveau du code. D'après ce que j'ai vu, cela ne change rien.
Par exemple, j'ai une fonction max(a,b) et un autre déclarée inline max2(a,b).
J'ai posté en bas ce que je vois dans l'assembly, ayant appelé les fonctions dans main comme ceci:
le code des deux fonctions est respectivement:Code:
1
2
3
4
5
6
7 int main(){ int al=5,bl=0; int mo=max(al,bl); mo=max2(al,bl); }
Code:
1
2
3
4
5
6
7 int max(int a,int b){ return a>b?a:b; } inline int max2(int a,int b){ return a>b?a:b; }
Au niveau du code assembleur des deux fonctions, c'est aussi pareil (je ne l'ai pas posté).
Mais peut-être que la différence n'est pas visible au niveau de l'assembly, (ou que qqch n'est pas paramétré dans mon compilateur).
Merci
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 52: int mo=max(al,bl); 009116BC 8B 45 EC mov eax,dword ptr [bl] 009116BF 50 push eax 009116C0 8B 4D F8 mov ecx,dword ptr [al] 009116C3 51 push ecx 009116C4 E8 5E FA FF FF call max (911127h) 009116C9 83 C4 08 add esp,8 009116CC 89 45 E0 mov dword ptr [mo],eax 53: 54: o=max2(al,bl); 009116CF 8B 45 EC mov eax,dword ptr [bl] 009116D2 50 push eax 009116D3 8B 4D F8 mov ecx,dword ptr [al] 009116D6 51 push ecx 009116D7 E8 4C F9 FF FF call max2 (911028h) 009116DC 83 C4 08 add esp,8 009116DF 89 45 E0 mov dword ptr [mo],eax
Pour comprendre réellement ce que fait l'inlining, il faut commencer par s'intéresser au code binaire (allez, nous pouvons nous arrêter au niveau de l'assembleur :D) généré.
Lors d'un appel de fonction "normal", il y a trois étapes principales:
Le mot clé inline a pour objectif de demander au compilateur de remplacer l'appel d'une fonction par le code exécutable de celle-ci.
- le placement sur la pile des informations de contexte de la fonction appelante (adresse de l'instruction suivante, valeur éventuelle des registres, ...) et "saut" à l'adresse mémoire à laquelle débute la fonction appelée
- exécution de la fonction appelée
- récupération des informations de contexte de la fonction appelante
Pour faire simple, disons que, cela revient à remplacer l'appel de la fonction appelée par l'étape (2) directement au sein de la fonction appelante.
Le gain observé est donc de l'ordre du temps nécessaire à effectuer les push de l'étape 1 et les pop de l'étape 3.
L'avantage ressenti par l'utilisateur sera d'autant plus important à l'exécution queCependant, le mot clé inline ne fait que demander au compilateur de procéder de la sorte, et il est tout à fait en mesure de choisir s'il le fait ou non en fonction du nombre d'instructions (je parle du nombre d'instructions processeur ;) ) qui sont nécessaires pour effectuer l'ensemble de la fonction appelée.
- la fonction appelée est petite
- la fonction est appelée souvent
Le principe est relativement simple: il appliquera le mot clé inline (s'il est présent) pour toute fonction demandant moins d'un certain nombre d'instructions, et ne l'appliquera plus dés que la fonction en demande d'avantage (il est cependant possible de modifier la limite au niveau des options de compilation).
Ainsi, un accesseur "simple" commeou une fonction libre peu complexe commeCode:type const & Classe::getType() const{return t_;}
aura beaucoup plus de chance d'être effectivement inlinée qu'une fonction plus complexeCode:int max(int i1, int i2){return i1<i2? i2:i1;
De plus, une fonction récursive aura très peu de chance d'être effectivement inlinée, même si elle est simple car il est vraiment difficile de déterminer le nombre de récursivités qu'il faudra mettre en oeuvre.
Il ne sert pas à grand chose d'utiliser la récursivité pour la factorielle, mais la fonction
ne sera sans doute pas inlinée, même si elle était codée sous la forme deCode:
1
2
3
4
5
6 inline int factorielle(int i) { if(i==1) return i; return i*factorielle(i-1); }
(en tout cas, tant que l'on ne fait pas en plus entrer la généricité en ligne de compte :P)Code:
1
2
3
4 inline int factorielle(int i) { return i==1 i : i*factorielle(i-1); }
C'est un peu à cela que sert (en théorie du moins) le mot clef "inline"... Si le compilateur peut facilement juger de la taille du code exécutable d'une fonction, il ne peut connaitre le nombre d'appels à celle ci (il faut pour cela un profileur). Il ne sait généralement pas, non plus (mais je crois que cela change) inliner des fonctions se trouvant dans la boucle intérieure d'un algorithme (souvent la vitesse réelle d'un algorithme dépend de la rapidité de sa boucle intérieure, un appel de fonction, même petite, à l'intérieur peut faire une grosse différence).
"inline" est censé être un moyen pour le programmeur de préciser au compilateur qu'une fonction "mériterait" d'être inlinée, bien qu'elle ne soit pas très courte.
Un autre cas où "inline" est utile, ce sont les templates : une fonction définie dans un entête de classe est automatiquement inlinée, sauf si elle appartient à un template...
Francois
Ce n'est pas ce que disent Josuttis et Vandevoorde (p.72)
"This may lead to the impression that function template are inline by default. However, they're not. If you write function templates that should be handled as inline functions, you should use the inline specifier (unless the function is inline already because it is defined inside a class definition)."
http://books.google.com/books?id=Eot...%2B%2B&f=false
Ce que j'en comprends c'est que les template sont une exception, dans le cas des fonctions (pas inlinées par défaut). Et je crois avoir observé ce comportement dans le cas de fonctions membres de classes template...
La seule chose garantie, je crois, c'est une fonction template membre d'une classe normale.
Il est bien sur possible que cela ait évolué depuis la sortie de ce livre (2003). Mais je connais personnellement quelques compilateurs qui, effectivement, n'inlinent pas par défaut tout le code template.
PS. Pour être précis, voici ce que j'ai retenu...
1- une fonction template définie dans un .h n'est pas inlinée par défaut (il faut le préciser)
2- une spécialisation explicite d'une fonction template inlinée par défaut ne l'est pas par défaut (il faut aussi le préciser)
3- dans le cas d'une classe template : si le code de la fonction se trouve à l'intérieur de la définition de classe (entre les deux {}), il sera inliné (je ne crois pas que ce soit systématique, cependant, le compilateur a probablement son mot à dire dans pas mal d'implémentations), s'il est défini plus loin dans le .h, il ne le sera pas
4- dans le cas des spécialisations d'une classe template, je ne sais pas, mais j'ai des doutes
Et même dans le cas où tout ceci est précisément défini dans la norme, j'aurais des doutes quant à l'implémentation sur les différents compilateurs. L'instanciation de templates, c'est notoirement subtil !
Francois
Salut,
Pour les templates, l'ODR est garanti :
Cependant, la norme précise que le seul cas où inline est nécessaire c'est pour les spécialisations explicites :Citation:
Envoyé par 3.2. ODR (¤5)
En revanche, je veux bien te croire quand tu dis que le comportement peut varier selon le compilo vu que les template ne sont pas forcément le plus facile à gérer.Citation:
Envoyé par 14.7.3 Explicit specialisation ¤14
@deubelte : l'inlining d'une fonction (au sens où l'appel est remplacé par le code) ne sera visible qu'en release. En général, l'option est désactivé en mode debug.
Le texte est assez clair : une fonction template définie dans une classe est inlinée.
Je suis d'accord que si elle est déclarée dans la classe et définie à l'extérieur, elle ne sera pas inlinée, puisqu'elle se comporte comme une fonction standard.
J'ai déjà vu le comportement sur plusieurs compilateurs (Intel et GCC). En mode débug, rien n'est inliné, en mode release, plusieurs fonctions sont inlinées (gain de 10 sur mon code hautement templaté par ex).
Je ne comprends pas... Ce cas précise la situation des spécialisation explicites d'une fonction template, il ne précise rien sur les autres cas.
Quid, par exemple des spécialisations explicites de classes... ?
1- je définis une classe template "de base", une fonction définie à l'intérieur de la définition de classe est inlinée, une en dehors ne l'est pas... (jusque là ca va)
2- je spécialise (éventuellement partiellement) cette classe, et spécialise donc ses membres (la norme m'y oblige, je crois), je suppose qu'alors le lieu où les fonctions spécialisées sont redéfinies prendra le pas sur la règle précédente pour décider de l'inlining. C'est garanti? C'est dans le standard?
3- j'ai dans une classe non template une fonction membre template (inlinée car définie dans le header), que je spécialise explicitement, y's'pass' quoi?
4- même question pour une classe template, ou sa spécialisation...
Et je pense qu'on peut continuer jusqu'à avoir très mal à la tête...
En fait, il me semble que le seul cas précis est celui des "fonctions template globales", tout le reste...
@Matthieu : sur le compilateur que j'utilise (Borland), on dispose d'une série d'options gérant l'inlining. C'est rendu nécessaire par la complexité du débugage, et ca change pas mal de choses à l'exécution. Mais j'ai remarqué qu'un passage avec un profileur montre que, même en "full inline", le compilateur fait parfois des choix...
Il est très possible qu'il y ait des règles précises (qu'un compilateur suivra ou non), mais je doute qu'elles soient simples...
Francois
Je pense qu'on peut déduire ça étant données les "règles" de base de déduction de quand une fonction est inlinée, effectivement.
Cf le post de 3DArchi, il faut préciser toi-même que tu veux qu'elle soit inlinée, car elle est considérée "différente" du modèle qu'elle spéciale au niveau de l'inlining.
Là du moment que tu définis ta fonction template à l'intérieur ça devrait être inliné par défaut :)
j'ai étudié moi même les fonctions inline, voila ce qu'il en ressort. Maintenant, j'ai compris ce qu'il se passait. (je fais références avec des couleurs, mais il
faut pas les prendre en compte).
Supposons que l’on ait une fonction, l’une déclaré inline l’autre non.
La fonction main est alors déclarée comme ceci :Code:
1
2
3
4
5
6 int max(int a,int b){ return a>b?a:b; } inline int max2(int a,int b){ return a>b?a:b; }
Le code mémoire de ces deux fonctions sont :Code:
1
2
3
4
5 int main(){ int al=5,bl=0; int mo=max(al,bl); mo=max2(al,bl); }
-pour max2
-pour max :Code:
1
2
3
4 17: inline int max2(int a,int b){ 18: return a>b?a:b; 19: } 20:
Le code mémoire de la fonction main est :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 10: int max(int a,int b){ 00221060 55 push ebp 00221061 8B EC mov ebp,esp 00221063 51 push ecx 00221064 C7 45 FC CC CC CC CC mov dword ptr [ebp-4],0CCCCCCCCh 11: return a>b?a:b; 0022106B 8B 45 08 mov eax,dword ptr [a] 0022106E 3B 45 0C cmp eax,dword ptr [b] 00221071 7E 08 jle max+1Bh (22107Bh) 00221073 8B 4D 08 mov ecx,dword ptr [a] 00221076 89 4D FC mov dword ptr [ebp-4],ecx 00221079 EB 06 jmp max+21h (221081h) 0022107B 8B 55 0C mov edx,dword ptr [b] 0022107E 89 55 FC mov dword ptr [ebp-4],edx 00221081 8B 45 FC mov eax,dword ptr [ebp-4] 12: }
On voit bien l’absence de call, push, à la suite de la fonction max2, alors que ces mots clés existent bien à la suite de la fonction max (cf en rouge). On voit aussi que le code de la fonction max, (qui est identique à celui de max2) se retrouve dans le corps de la fonction main. Ce qui prouve bien que « le code complet de cette fonction est inséré dans le code de l’appelant, c’est le même principe qu’un #define. Cela peut, parmi beaucoup d'autres choses, améliorer les performances, étant donné que l'optimiseur peut intégrer directement le code appelé, voire optimiser le code appelé en fonction du code appelant. » On a donc représenté en vert le code de max2, qui correspond tout a fait au code de max. Cela permet d’éliminer tous les mots cléfs en bleu.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 50: int al=5,bl=0; 002211E7 C7 45 FC 05 00 00 00 mov dword ptr [al],5 002211EE C7 45 F8 00 00 00 00 mov dword ptr [bl],0 51: 52: int mo=max(al,bl); 002211F5 8B 45 F8 mov eax,dword ptr [bl] 002211F8 50 push eax 002211F9 8B 4D FC mov edx,dword ptr [al] 002211FC 51 push ecx 002211FD E8 1C FE FF FF call max (22101Eh) 00221202 83 C4 08 add esp,8 00221205 89 45 F4 mov dword ptr [mo],eax 53: 54: mo=max2(al,bl); 00221208 8B 55 FC mov edx,dword ptr [al] 0022120B 3B 55 F8 cmp edx,dword ptr [bl] 0022120E 7E 08 jle main+48h (221218h) 00221210 8B 45 FC mov eax,dword ptr [al] 00221213 89 45 AC mov dword ptr [ebp-54h],eax 00221216 EB 06 jmp main+4Eh (22121Eh) 00221218 8B 4D F8 mov ecx,dword ptr [bl] 0022121B 89 4D AC mov dword ptr [ebp-54h],ecx 0022121E 8B 55 AC mov edx,dword ptr [ebp-54h] 00221221 89 55 F4 mov dword ptr [mo],edx
Attention, la présence du 0xCCCCCCCC montre que c'est encore une compilation en Debug.