J'ai eu cette question lorsque j'ai reçu un commentaire de révision de code disant que les fonctions virtuelles ne doivent pas nécessairement être en ligne.
Je pensais que les fonctions virtuelles en ligne pourraient être utiles dans les scénarios où les fonctions sont appelées directement sur des objets. Mais le contre-argument m'est venu à l'esprit: pourquoi voudrait-on définir le virtuel puis utiliser des objets pour appeler des méthodes?
Est-il préférable de ne pas utiliser les fonctions virtuelles en ligne, car elles ne sont pratiquement jamais développées de toute façon?
Extrait de code que j'ai utilisé pour l'analyse:
class Temp
{
public:
virtual ~Temp()
{
}
virtual void myVirtualFunction() const
{
cout<<"Temp::myVirtualFunction"<<endl;
}
};
class TempDerived : public Temp
{
public:
void myVirtualFunction() const
{
cout<<"TempDerived::myVirtualFunction"<<endl;
}
};
int main(void)
{
TempDerived aDerivedObj;
//Compiler thinks it's safe to expand the virtual functions
aDerivedObj.myVirtualFunction();
//type of object Temp points to is always known;
//does compiler still expand virtual functions?
//I doubt compiler would be this much intelligent!
Temp* pTemp = &aDerivedObj;
pTemp->myVirtualFunction();
return 0;
}
c++
inline
virtual-functions
un J.
la source
la source
pTemp->myVirtualFunction()
pouvait être résolu comme un appel non virtuel, il pourrait avoir en ligne cet appel. Cet appel référencé est incorporé par g ++ 3.4.2:TempDerived & pTemp = aDerivedObj; pTemp.myVirtualFunction();
Votre code ne l'est pas.Réponses:
Les fonctions virtuelles peuvent parfois être intégrées. Un extrait de l'excellente FAQ C ++ :
la source
C ++ 11 a été ajouté
final
. Cela change la réponse acceptée: il n'est plus nécessaire de connaître la classe exacte de l'objet, il suffit de savoir que l'objet a au moins le type de classe dans lequel la fonction a été déclarée finale:la source
icc
semble le faire, selon ce lien.Il existe une catégorie de fonctions virtuelles où il est toujours logique de les avoir en ligne. Prenons le cas suivant:
L'appel pour supprimer 'base', effectuera un appel virtuel pour appeler le destructeur de classe dérivé correct, cet appel n'est pas en ligne. Cependant, comme chaque destructeur appelle son destructeur parent (qui dans ces cas est vide), le compilateur peut incorporer ces appels, car ils n'appellent pas virtuellement les fonctions de la classe de base.
Le même principe existe pour les constructeurs de classes de base ou pour tout ensemble de fonctions où l'implémentation dérivée appelle également l'implémentation des classes de base.
la source
J'ai vu des compilateurs qui n'émettent aucune v-table si aucune fonction non en ligne n'existe (et définie dans un fichier d'implémentation au lieu d'un en-tête alors). Ils jetteraient des erreurs comme
missing vtable-for-class-A
ou quelque chose de similaire, et vous seriez complètement confus, comme moi.En effet, ce n'est pas conforme à la norme, mais cela arrive alors pensez à mettre au moins une fonction virtuelle pas dans l'en-tête (ne serait-ce que le destructeur virtuel), afin que le compilateur puisse émettre une vtable pour la classe à cet endroit. Je sais que cela arrive avec certaines versions de
gcc
.Comme quelqu'un l'a mentionné, les fonctions virtuelles en ligne peuvent parfois être un avantage , mais bien sûr, le plus souvent, vous les utiliserez lorsque vous ne connaissez pas le type dynamique de l'objet, car c'était la raison pour laquelle
virtual
en premier lieu.Le compilateur ne peut cependant pas complètement ignorer
inline
. Il a une autre sémantique que l'accélération d'un appel de fonction. L' inline implicite pour les définitions en classe est le mécanisme qui vous permet de mettre la définition dans l'en-tête: seules lesinline
fonctions peuvent être définies plusieurs fois dans l'ensemble du programme sans violation des règles. En fin de compte, il se comporte comme vous ne l'auriez défini qu'une seule fois dans l'ensemble du programme, même si vous avez inclus l'en-tête plusieurs fois dans différents fichiers liés entre eux.la source
Eh bien, en fait, les fonctions virtuelles peuvent toujours être intégrées , tant qu'elles sont liées statiquement entre elles: supposons que nous ayons une classe abstraite
Base
avec une fonction virtuelleF
et des classes dérivéesDerived1
etDerived2
:Un appel hypotétique
b->F();
(avecb
de typeBase*
) est évidemment virtuel. Mais vous (ou le compilateur ...) pourriez le réécrire comme tel (supposons que cetypeof
soit unetypeid
fonction semblable à celle qui renvoie une valeur qui peut être utilisée dans aswitch
)alors que nous avons toujours besoin de RTTI pour le
typeof
, l'appel peut effectivement être intégré en intégrant la vtable dans le flux d'instructions et en spécialisant l'appel pour toutes les classes impliquées. Cela pourrait également être généralisé en ne spécialisant que quelques classes (disons, justeDerived1
):la source
Le marquage d'une méthode virtuelle en ligne aide à optimiser davantage les fonctions virtuelles dans les deux cas suivants:
Modèle de modèle curieusement récurrent ( http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurring-Template-Patt )
Remplacement des méthodes virtuelles par des modèles ( http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html )
la source
inline ne fait vraiment rien - c'est un indice. Le compilateur peut l'ignorer ou il peut insérer un événement d'appel sans en ligne s'il voit l'implémentation et aime cette idée. Si la clarté du code est en jeu, l' inline doit être supprimée.
la source
Les fonctions virtuelles déclarées incorporées sont incorporées lorsqu'elles sont appelées via des objets et ignorées lorsqu'elles sont appelées via un pointeur ou des références.
la source
Avec les compilateurs modernes, cela ne fera aucun mal de les intégrer. Certains anciens combos compilateur / éditeur de liens ont peut-être créé plusieurs vtables, mais je ne pense plus que ce soit un problème.
la source
Un compilateur ne peut intégrer une fonction que lorsque l'appel peut être résolu sans ambiguïté au moment de la compilation.
Les fonctions virtuelles, cependant, sont résolues au moment de l'exécution, et le compilateur ne peut donc pas intégrer l'appel, car au type de compilation, le type dynamique (et donc l'implémentation de la fonction à appeler) ne peut pas être déterminé.
la source
Dans les cas où l'appel de fonction est sans ambiguïté et la fonction un candidat approprié pour l'inlining, le compilateur est assez intelligent pour incorporer le code de toute façon.
Le reste du temps "virtuel en ligne" est un non-sens, et en effet certains compilateurs ne compileront pas ce code.
la source
Il est logique de créer des fonctions virtuelles, puis de les appeler sur des objets plutôt que sur des références ou des pointeurs. Scott Meyer recommande, dans son livre "effective c ++", de ne jamais redéfinir une fonction non virtuelle héritée. Cela a du sens, car lorsque vous créez une classe avec une fonction non virtuelle et que vous redéfinissez la fonction dans une classe dérivée, vous pouvez être sûr de l'utiliser correctement vous-même, mais vous ne pouvez pas être sûr que les autres l'utiliseront correctement. En outre, vous pouvez à une date ultérieure l'utiliser de manière incorrecte vous-même. Donc, si vous créez une fonction dans une classe de base et que vous voulez qu'elle soit redifinable, vous devez la rendre virtuelle. S'il est judicieux de créer des fonctions virtuelles et de les appeler sur des objets, il est également logique de les intégrer.
la source
En fait, dans certains cas, l'ajout «en ligne» à un remplacement final virtuel peut empêcher votre code de se compiler, il y a donc parfois une différence (au moins sous le compilateur VS2017)!
En fait, je faisais une fonction de remplacement final en ligne virtuelle dans VS2017 en ajoutant la norme c ++ 17 pour compiler et lier et pour une raison quelconque, cela a échoué lorsque j'utilise deux projets.
J'avais un projet de test et une DLL d'implémentation que je teste unitaire. Dans le projet de test, j'ai un fichier "linker_includes.cpp" qui #inclut les fichiers * .cpp de l'autre projet qui sont nécessaires. Je sais ... Je sais que je peux configurer msbuild pour utiliser les fichiers objets de la DLL, mais gardez à l'esprit qu'il s'agit d'une solution spécifique à Microsoft, tandis que l'inclusion des fichiers cpp n'est pas liée à build-system et beaucoup plus facile à version un fichier cpp que des fichiers xml et des paramètres de projet et autres ...
Ce qui était intéressant, c'est que j'obtenais constamment des erreurs de l'éditeur de liens du projet de test. Même si j'ai ajouté la définition des fonctions manquantes par copier-coller et non par include! Si étrange. L'autre projet a été construit et il n'y a aucun lien entre les deux autres que le marquage d'une référence de projet, il y a donc un ordre de construction pour s'assurer que les deux sont toujours construits ...
Je pense que c'est une sorte de bogue dans le compilateur. Je n'ai aucune idée s'il existe dans le compilateur livré avec VS2020, car j'utilise une version plus ancienne car certains SDK ne fonctionnent que correctement :-(
Je voulais juste ajouter que non seulement les marquer comme inline peut signifier quelque chose, mais peut même empêcher votre code de se construire dans de rares circonstances! C'est étrange, mais bon à savoir.
PS: Le code sur lequel je travaille est lié à l'infographie, donc je préfère l'inlining et c'est pourquoi j'ai utilisé à la fois final et inline. J'ai gardé le spécificateur final pour espérer que la version de la version est suffisamment intelligente pour créer la DLL en l'incrustant même sans que je le laisse directement entendre ...
PS (Linux).: Je pense que la même chose ne se produit pas dans gcc ou clang, car j'avais l'habitude de faire ce genre de choses. Je ne sais pas d'où vient ce problème ... Je préfère faire du C ++ sous Linux ou du moins avec certains gcc, mais parfois le projet a des besoins différents.
la source