web-dev-qa-db-fra.com

Les fonctions virtuelles en ligne sont-elles vraiment un non-sens?

J'ai eu cette question lorsque j'ai reçu un commentaire de révision de code indiquant 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 directement appelées par des objets. Mais le contre-argument qui m'est venu à l’esprit est le suivant: 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 presque 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;
}
163
aJ.

Les fonctions virtuelles peuvent être parfois en ligne. Un extrait de l'excellent C++ faq :

"Un appel virtuel en ligne ne peut être en ligne que lorsque le compilateur connaît la" classe exacte "de l'objet qui est la cible de l'appel de la fonction virtuelle. Cela ne peut se produire que lorsque le compilateur a un objet réel plutôt qu'un pointeur ou référence à un objet, c’est-à-dire avec un objet local, un objet global/statique ou un objet entièrement contenu dans un composite. "

142
ya23

C++ 11 a 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:

class A { 
  virtual void foo();
};
class B : public A {
  inline virtual void foo() final { } 
};
class C : public B
{
};

void bar(B const& b) {
  A const& a = b; // Allowed, every B is an A.
  a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C.
}
70
MSalters

Il existe une catégorie de fonctions virtuelles pour lesquelles il est toujours judicieux de les avoir en ligne. Considérons le cas suivant:

class Base {
public:
  inline virtual ~Base () { }
};

class Derived1 : public Base {
  inline virtual ~Derived1 () { } // Implicitly calls Base::~Base ();
};

class Derived2 : public Derived1 {
  inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 ();
};

void foo (Base * base) {
  delete base;             // Virtual call
}

L'appel à supprimer 'base' effectuera un appel virtuel pour appeler le destructeur de classe dérivée correct, cet appel n'est pas en ligne. Cependant, comme chaque destructeur appelle son destructeur parent (qui dans ce cas est vide), le compilateur peut appeler ceux-ci, car ils n'appellent pas virtuellement les fonctions de 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 de classes de base.

36
Richard Corden

J'ai vu des compilateurs n'émettant aucune v-table si aucune fonction non-inline n'existe (et définie dans un fichier d'implémentation au lieu d'un en-tête). Ils jetteraient des erreurs comme missing vtable-for-class-A ou quelque chose de similaire, et vous seriez confus comme moi.

En effet, ce n'est pas conforme à la norme, mais cela arrive, pensez donc à mettre au moins une fonction virtuelle qui ne se trouve pas dans l'en-tête (si ce n'est que le destructeur virtuel), afin que le compilateur puisse émettre une table virtuelle 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 présenter un avantage parfois, mais bien entendu, vous l'utiliserez le plus souvent not connaître le type dynamique de l'objet, car était la raison principale pour virtual en premier lieu.

Le compilateur ne peut cependant pas ignorer complètement inline. Il a d'autres sémantiques en plus d'accélérer un appel de fonction. Le inline implicite pour les définitions in-class est le mécanisme qui vous permet de mettre la définition dans l'en-tête: seules les fonctions inline peuvent être définies plusieurs fois dans l'ensemble du programme sans violation, aucune règles. En fin de compte, il se comporte comme vous ne l'aviez défini qu'une 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.

14

Eh bien, en fait , les fonctions virtuelles peuvent toujours être en ligne , aussi longtemps qu'elles sont liées statiquement: supposons que nous ayons une classe abstraite Base avec une fonction virtuelle F et des classes dérivées Derived1 et Derived2:

class Base {
  virtual void F() = 0;
};

class Derived1 : public Base {
  virtual void F();
};

class Derived2 : public Base {
  virtual void F();
};

Un appel hypotétique b->F(); (avec b de type Base*) Est évidemment virtuel. Mais vous (ou le compilateur ...) pourriez le réécrire comme suit (supposons que typeof soit une fonction de type typeid qui renvoie une valeur utilisable dans a switch)

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // static, inlineable call
  case Derived2: b->Derived2::F(); break; // static, inlineable call
  case Base:     assert(!"pure virtual function call!");
  default:       b->F(); break; // virtual call (dyn-loaded code)
}

bien que nous ayons encore besoin de RTTI pour le typeof, l'appel peut être effectivement intégré en incorporant la table vtable dans le flux d'instructions et en spécialisant l'appel pour toutes les classes impliquées. Cela pourrait être aussi généralisé en ne spécialisant que quelques classes (disons, seulement Derived1):

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // hot path
  default:       b->F(); break; // default virtual call, cold path
}
10
CAFxX

inline ne fait vraiment rien - c'est un indice. Le compilateur peut l’ignorer ou insérer un événement d’appel sans inline s’il voit l’implémentation et aime cette idée. Si la clarté du code est en jeu, le inline doit être supprimé.

3
sharptooth

Le marquage d’une méthode virtuelle en ligne permet d’optimiser davantage les fonctions virtuelles dans les deux cas suivants:

3
Chenna Reddy

Les fonctions virtuelles déclarées en ligne sont en ligne lorsqu'elles sont appelées via des objets et ignorées lorsqu'elles sont appelées via un pointeur ou des références.

3
tarachandverma

Avec les compilateurs modernes, il ne fait aucun mal de les intégrer. Certains anciens combos compilateur/éditeur de liens auraient pu créer plusieurs vtables, mais je ne crois pas que ce soit un problème.

1
anon

Dans les cas où l'appel de fonction est sans ambiguïté et que la fonction est un candidat approprié pour l'intégration, le compilateur est suffisamment intelligent pour intégrer 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.

1
moonshadow

Un compilateur ne peut intégrer une fonction que lorsque l'appel peut être résolu sans ambiguïté lors de la compilation.

Les fonctions virtuelles sont toutefois résolues au moment de l'exécution et le compilateur ne peut donc pas inscrire l'appel en ligne, car au type de compilation, le type dynamique (et donc l'implémentation de la fonction à appeler) ne peut pas être déterminé.

0
PaulJWilliams

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 la redéfinissez 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 pourrez ultérieurement l’utiliser de manière incorrecte. Ainsi, si vous créez une fonction dans une classe de base et que vous souhaitez la redéfinir, vous devez la rendre virtuelle. S'il est logique de créer des fonctions virtuelles et de les appeler sur des objets, il est également judicieux de les aligner.

0
Balthazar