Comment les fonctions virtuelles et vtable sont-elles mises en œuvre?

nous savons tous ce que sont les fonctions virtuelles en C++, mais comment sont-elles implémentées à un niveau profond?

le vtable peut-il être modifié ou même directement consulté à l'exécution?

est-ce que le vtable existe pour toutes les classes, ou seulement celles qui ont au moins une fonction virtuelle?

Faire les classes abstraites ont simplement une valeur NULL le pointeur de fonction d'au moins une entrée?

le Fait d'avoir un seul virtuelle la fonction de ralentir l'ensemble de la classe? Ou seulement l'appel à la fonction qui est virtuel? Et la vitesse affectée si la fonction virtuelle est en fait remplacé ou pas, ou n'ont aucun effet tant que c'est virtuel.

90
demandé sur Aquarius_Girl 2008-09-19 07:29:44

12 réponses

comment les fonctions virtuelles sont-elles mises en œuvre à un niveau profond?

à Partir de "Fonctions Virtuelles en C++"

chaque fois qu'un programme a une fonction virtuelle déclarée, une table en v est construite pour la classe. La v-table se compose des adresses aux fonctions virtuelles pour les classes qui contiennent une ou plusieurs fonctions virtuelles. L'objet de la classe contenant la fonction virtuelle contient un pointeur virtuel qui pointe vers la base l'adresse de la table virtuelle en mémoire. Chaque fois qu'il y a un appel de fonction virtuel, la table en v est utilisée pour résoudre à l'adresse de fonction. Un objet de la classe qui contient un ou plusieurs des fonctions virtuelles contient un pointeur virtuel appelé le vptr au tout début de l'objet dans la mémoire. Donc la taille de l'objet dans ce cas augmente en fonction de la taille du pointeur. Cette vptr contient l'adresse de base de la table virtuelle en mémoire. Notez que les tables virtuelles sont spécifiques à la classe, c'est-à-dire, il y a une seule table virtuelle pour une classe quel que soit le nombre de fonctions virtuelles qu'il contient. Cette table virtuelle contient les adresses de base d'une ou de plusieurs fonctions virtuelles de la classe. Au moment où une fonction virtuelle est appelée sur un objet, le vptr de cet objet fournit l'adresse de base de la table virtuelle de cette catégorie dans la mémoire. Cette table est utilisée pour résoudre l'appel de fonction car elle contient les adresses de toutes les fonctions virtuelles de cette classe. C'est de cette façon liaison dynamique est résolu lors d'un appel de fonction virtuelle.

le vtable peut-il être modifié ou même directement consulté à l'exécution?

Universellement, je crois que la réponse est "non". Vous pourriez faire quelques manipulations de mémoire pour trouver le vtable, mais vous ne sauriez toujours pas à quoi ressemble la fonction signature pour l'appeler. Tout ce que vous souhaiteriez réaliser avec cette capacité (que la langue supporte) devrait être possible sans accéder directement à la vtable ou de le modifier lors de l'exécution. Notez également que la spécification du langage C++ ne spécifie pas que les vtables sont nécessaires - mais c'est la façon dont la plupart des compilateurs implémentent les fonctions virtuelles.

est-ce que le vtable existe pour tous les objets, ou seulement ceux qui ont au moins une fonction virtuelle?

je croire ici, la réponse est "ça dépend de la mise en œuvre" depuis que la norme n'exige pas vtables en premier lieu. Cependant, dans la pratique, je crois que tous les compilateurs modernes ne créent un vtable que si une classe A au moins 1 fonction virtuelle. Il y a un espace au-dessus de la table et un espace au-dessus du temps associé à l'appel d'une fonction virtuelle par rapport à une fonction non virtuelle.

Faire les classes abstraites ont simplement une valeur NULL le pointeur de fonction d'au moins une entrée?

la réponse est qu'il n'est pas spécifié par la spécification de la langue, il dépend donc de la mise en œuvre. Appeler la fonction virtuelle pure donne un comportement non défini si elle n'est pas définie (ce qui n'est généralement pas le cas) (ISO/IEC 14882:2003 10.4-2). Dans la pratique, il attribue une fente dans le vtable pour la fonction mais ne lui assigne pas d'adresse. Cela laisse le vtable incomplet qui nécessite les classes dérivées pour implémenter la fonction et compléter le vtable. Certaines implémentations placent simplement un pointeur NULL dans l'entrée vtable; d'autres implémentations placent un pointeur vers une méthode factice qui ne quelque chose de semblable à une affirmation.

notez qu'une classe abstraite peut définir une implémentation pour une fonction virtuelle pure, mais que cette fonction ne peut être appelée qu'avec une syntaxe qualified-id (ie. entièrement spécifiant la classe dans le nom de la méthode, similaire à l'appel d'une méthode de classe de base d'une classe dérivée). Ceci est fait pour fournir une implémentation par défaut facile à utiliser, tout en exigeant qu'une classe dérivée fournisse une dérogation.

le Fait d'avoir une seule fonction virtuelle ralentir l'ensemble de la classe ou seulement l'appel à la fonction qui est virtuel?

cela va à la limite de mes connaissances, donc quelqu'un s'il vous plaît aidez-moi ici si je me trompe!

je croire que seules les fonctions qui sont virtuels dans la catégorie de l'expérience à la fois de performances liées à l'appel d'une fonction virtuelle par rapport à un non-fonction virtuelle. L'espace au-dessus de la classe est là de toute façon. Notez que s'il y a un vtable, il n'y en a qu'un par classe , pas un par objet .

la vitesse affectée si la fonction virtuelle est en fait remplacée ou pas, ou n'ont aucun effet tant que c'est virtuel?

Je ne crois pas que le temps d'exécution d'une fonction virtuelle qui est dépassée diminue par rapport à l'appel de la fonction virtuelle de base. Cependant, il y a un espace supplémentaire overhead pour la classe associée à la définition d'un autre vtable pour la classe dérivée vs la classe de base.

Ressources Supplémentaires:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (via machine de retour))

http://en.wikipedia.org/wiki/Virtual_table

http://www.codesourcery.com/public/cxx-abi/abi.html#vtable

107
répondu Zach Burlingame 2015-04-09 19:47:49
  • le vtable peut-il être modifié ou même directement consulté à l'exécution?

pas forcément, mais si ça ne vous dérange pas, bien sûr!

avertissement : cette technique n'est pas recommandée pour les enfants, les adultes de moins de 969 , ou les petites créatures à fourrure D'Alpha Centauri. Les effets secondaires peuvent inclure démons qui volent hors de votre nez , l'apparition abrupte de Yog-Sothoth comme Approbation requise sur toutes les révisions de code subséquentes, ou l'ajout rétroactif de IHuman::PlayPiano() à toutes les instances existantes]

dans la plupart des compilateurs que j'ai vus, le vtbl * est les 4 premiers octets de l'objet, et le contenu de vtbl est simplement un tableau de pointeurs de membres (généralement dans l'ordre où ils ont été déclarés, avec la première classe de base). Il y a bien sûr d'autres configurations possibles, mais c'est ce que j'ai généralement observé.

class A {
  public:
  virtual int f1() = 0;
};
class B : public A {
  public:
  virtual int f1() { return 1; }
  virtual int f2() { return 2; }
};
class C : public A {
  public:
  virtual int f1() { return -1; }
  virtual int f2() { return -2; }
};

A *x = new B;
A *y = new C;
A *z = new C;

maintenant pour tirer quelques manigances...

changement de classe à l'exécution:

std::swap(*(void **)x, *(void **)y);
// Now x is a C, and y is a B! Hope they used the same layout of members!

Remplacement d'une méthode pour toutes les instances (monkeypatching une classe)

celui-ci est un peu plus délicat, puisque la vtbl elle-même est probablement en mémoire en lecture seule.

int f3(A*) { return 0; }

mprotect(*(void **)x,8,PROT_READ|PROT_WRITE|PROT_EXEC);
// Or VirtualProtect on win32; this part's very OS-specific
(*(int (***)(A *)x)[0] = f3;
// Now C::f1() returns 0 (remember we made x into a C above)
// so x->f1() and z->f1() both return 0

, ce dernier est plutôt probable pour faire virus-checkers et le lien Se réveiller et prendre note, en raison des manipulations mprotect. Dans un processus utilisant le bit NX, il peut très bien échouer.

26
répondu puetzk 2018-09-17 15:03:30

est-ce que le fait d'avoir une seule fonction virtuelle ralentit toute la classe?

ou seulement l'appel à la fonction qui est virtuelle? Et la vitesse affectée si la fonction virtuelle est en fait remplacé ou pas, ou n'ont aucun effet tant que c'est virtuel.

ayant des fonctions virtuelles ralentit toute la classe dans la mesure où un élément supplémentaire de données doit être initialisé, copié, ... en traitant avec un objet de cette classe. Pour une classe avec une demi-douzaine de membres, la différence devrait être négligeable. Pour une classe qui ne contient qu'un seul membre char , ou aucun membre du tout, la différence pourrait être notable.

en dehors de cela, il est important de noter que tous les appels à une fonction virtuelle ne sont pas des appels à une fonction virtuelle. Si vous avez un objet d'un type connu, le compilateur peut émettre du code pour une invocation de fonction normale, et peut même mettre en ligne ladite fonction. si elle se sent comme elle. Ce n'est que lorsque vous faites des appels polymorphes, via un pointeur ou une référence qui pourrait pointer vers un objet de la classe de base ou un objet d'une classe dérivée, que vous avez besoin de la fonction indirecte vtable et que vous payez en termes de performance.

struct Foo { virtual ~Foo(); virtual int a() { return 1; } };
struct Bar: public Foo { int a() { return 2; } };
void f(Foo& arg) {
  Foo x; x.a(); // non-virtual: always calls Foo::a()
  Bar y; y.a(); // non-virtual: always calls Bar::a()
  arg.a();      // virtual: must dispatch via vtable
  Foo z = arg;  // copy constructor Foo::Foo(const Foo&) will convert to Foo
  z.a();        // non-virtual Foo::a, since z is a Foo, even if arg was not
}

les étapes que le matériel doit suivre sont essentiellement les mêmes, que la fonction soit écrasée ou non. L'adresse du vtable est lue à partir de l'objet, le pointeur de fonction est récupéré de la fente appropriée, et la fonction appelée par pointeur. En ce qui concerne le rendement réel, les prévisions de la Direction générale pourraient avoir un certain impact. Ainsi, par exemple, si la plupart de vos objets se réfèrent à la même implémentation d'une fonction virtuelle donnée, alors il y a une certaine chance que le prédicteur de branche prévoie correctement quelle fonction appeler même avant que le pointeur ait été récupéré. Mais peu importe quelle fonction est commune: il peut s'agir de la plupart des objets déléguant au non-réécrit cas de base, ou la plupart des objets appartenant à la même sous-classe et donc déléguant au même cas réécrit.

comment sont-ils mis en œuvre à un niveau profond?

j'aime l'idée de jheriko de démontrer ceci en utilisant une implémentation simulée. Mais J'utiliserais C pour implémenter quelque chose qui s'apparente au code ci-dessus, pour que le bas niveau soit plus facilement visible.

parent de la classe Foo

typedef struct Foo_t Foo;   // forward declaration
struct slotsFoo {           // list all virtual functions of Foo
  const void *parentVtable; // (single) inheritance
  void (*destructor)(Foo*); // virtual destructor Foo::~Foo
  int (*a)(Foo*);           // virtual function Foo::a
};
struct Foo_t {                      // class Foo
  const struct slotsFoo* vtable;    // each instance points to vtable
};
void destructFoo(Foo* self) { }     // Foo::~Foo
int aFoo(Foo* self) { return 1; }   // Foo::a()
const struct slotsFoo vtableFoo = { // only one constant table
  0,                                // no parent class
  destructFoo,
  aFoo
};
void constructFoo(Foo* self) {      // Foo::Foo()
  self->vtable = &vtableFoo;        // object points to class vtable
}
void copyConstructFoo(Foo* self,
                      Foo* other) { // Foo::Foo(const Foo&)
  self->vtable = &vtableFoo;        // don't copy from other!
}

classe dérivée Bar

typedef struct Bar_t {              // class Bar
  Foo base;                         // inherit all members of Foo
} Bar;
void destructBar(Bar* self) { }     // Bar::~Bar
int aBar(Bar* self) { return 2; }   // Bar::a()
const struct slotsFoo vtableBar = { // one more constant table
  &vtableFoo,                       // can dynamic_cast to Foo
  (void(*)(Foo*)) destructBar,      // must cast type to avoid errors
  (int(*)(Foo*)) aBar
};
void constructBar(Bar* self) {      // Bar::Bar()
  self->base.vtable = &vtableBar;   // point to Bar vtable
}

la fonction f effectuant appel de fonction virtuelle

void f(Foo* arg) {                  // same functionality as above
  Foo x; constructFoo(&x); aFoo(&x);
  Bar y; constructBar(&y); aBar(&y);
  arg->vtable->a(arg);              // virtual function call
  Foo z; copyConstructFoo(&z, arg);
  aFoo(&z);
  destructFoo(&z);
  destructBar(&y);
  destructFoo(&x);
}

ainsi, vous pouvez voir, un vtable est juste un bloc statique en mémoire, la plupart du temps contenant des pointeurs de fonction. Chaque objet d'une classe polymorphique indiquera le vtable correspondant à son type dynamique. Cela rend également la connexion entre RTTI et les fonctions virtuelles plus claire: vous pouvez vérifier quel type d'une classe est simplement en regardant ce que vtable il pointe. Le ci-dessus est simplifié à bien des égards, comme par exemple l'héritage multiple, mais le concept général est solide.

si arg est de type Foo* et vous prenez arg->vtable , mais est en fait un objet de type Bar , alors vous obtenez toujours l'adresse correcte du vtable . C'est parce que le vtable est toujours le premier élément à l'adresse de l'objet, qu'il soit appelé vtable ou base.vtable dans une expression correctement dactylographiée.

14
répondu MvG 2016-12-22 10:59:48

généralement avec un VTable, un tableau de pointeurs vers les fonctions.

2
répondu Lou Franco 2008-09-19 03:31:21

cette réponse a été incorporée dans la Community Wiki answer

  • les classes abstraites ont-elles simplement un zéro pour le pointeur de fonction d'au moins une entrée?

la réponse est qu'elle n'est pas spécifiée - appeler la fonction virtuelle pure se traduit par un comportement non défini si elle n'est pas définie (ce qui n'est généralement pas le cas) (ISO/IEC 14882:2003 10.4-2). Quelque les implémentations placent simplement un pointeur NULL dans l'entrée vtable; d'autres implémentations placent un pointeur vers une méthode factice qui fait quelque chose de similaire à une assertion.

notez qu'une classe abstraite peut définir une implémentation pour une fonction virtuelle pure, mais que cette fonction ne peut être appelée qu'avec une syntaxe qualified-id (ie. entièrement spécifiant la classe dans le nom de la méthode, similaire à l'appel d'une méthode de classe de base d'une classe dérivée). Ceci est fait pour fournir un utilisez l'implémentation par défaut, tout en exigeant qu'une classe dérivée fournisse une dérogation.

2
répondu Michael Burr 2017-05-23 12:03:07

vous pouvez recréer la fonctionnalité des fonctions virtuelles en C++ en utilisant des pointeurs de fonction comme membres d'une classe et des fonctions statiques comme implémentations, ou en utilisant des pointeurs vers des fonctions de membre et des fonctions de membre pour les implémentations. Il n'y a que des avantages notionnels entre les deux méthodes... en fait, les appels de fonction virtuels sont tout simplement une commodité notationnelle eux-mêmes. En fait, l'héritage n'est qu'une commodité notionnelle... cela peut être mis en œuvre sans l'aide de l' caractéristiques linguistiques pour l'héritage. :)

ce qui suit est de la merde non testé, probablement code buggy, mais avec un peu de chance montre l'idée.

p.ex.

class Foo
{
protected:
 void(*)(Foo*) MyFunc;
public:
 Foo() { MyFunc = 0; }
 void ReplciatedVirtualFunctionCall()
 {
  MyFunc(*this);
 }
...
};

class Bar : public Foo
{
private:
 static void impl1(Foo* f)
 {
  ...
 }
public:
 Bar() { MyFunc = impl1; }
...
};

class Baz : public Foo
{
private:
 static void impl2(Foo* f)
 {
  ...
 }
public:
 Baz() { MyFunc = impl2; }
...
};
2
répondu jheriko 2008-09-19 14:41:32

je vais essayer de faire simple :)

nous savons tous ce que sont les fonctions virtuelles en C++, mais comment sont-elles mises en œuvre à un niveau profond?

C'est un tableau de pointeurs de fonctions, qui sont des implémentations d'une fonction virtuelle. Un indice dans ce tableau représente particulier l'indice d'une fonction virtuelle définie pour une classe. Cela inclut les fonctions virtuelles pures.

Lorsqu'une classe polymorphe dérive de une autre classe polymorphe, nous pouvons avoir les situations suivantes:

  • la classe dérivante n'ajoute pas de nouvelles fonctions virtuelles et n'en supplante aucune. Dans ce cas, cette catégorie partage le tableau avec la catégorie de base.
  • la classe dérivante ajoute et remplace les méthodes virtuelles. Dans ce cas, il obtient son propre vtable, où les fonctions virtuelles ajoutées ont index commençant après le dernier dérivé.
  • polymorphique Multiple les classes de l'héritage. Dans ce cas, nous avons un indice de décalage entre la deuxième et la prochaine bases et l'index dans la classe dérivée

le vtable peut-il être modifié ou même directement consulté à l'exécution?

pas standard way - il n'y a pas D'API pour y accéder. Les compilateurs peuvent avoir certaines extensions ou des API privées pour y accéder, mais ce peut être seulement une extension.

est-ce que le vtable existe pour toutes les classes, ou seulement ceux qui ont au moins une fonction virtuelle?

ceux qui ont au moins une fonction virtuelle (même destructeur) ou de dériver au moins une classe qui a son vtable ("est polymorphe").

Faire les classes abstraites ont simplement une valeur NULL le pointeur de fonction d'au moins une entrée?

C'est une implémentation possible, mais plutôt pas pratiquée. Au lieu de cela, il ya habituellement une fonction qui imprime quelque chose comme "pur virtuel fonction appelée" et ne abort() . L'appel à qui peut se produire si vous essayez d'appeler la méthode abstraite dans le constructeur ou le destructeur.

est-ce que le fait d'avoir une seule fonction virtuelle ralentit toute la classe? Ou seulement l'appel à la fonction qui est virtuel? Et la vitesse affectée si la fonction virtuelle est en fait remplacé ou pas, ou n'ont aucun effet tant que c'est virtuel.

le ralentissement ne dépend que de la l'appel est résolu comme appel direct ou comme appel virtuel. Et rien d'autre ne compte. :)

si vous appelez une fonction virtuelle par un pointeur ou une référence à un objet, alors elle sera toujours implémentée comme appel virtuel - parce que le compilateur ne peut jamais savoir quel type d'objet sera assigné à ce pointeur dans l'exécution, et s'il s'agit d'une classe dans laquelle cette méthode est dépassée ou non. Seulement dans deux cas, le compilateur peut résoudre l'appel d'une fonction virtuelle directement appel:

  • si vous appelez la méthode par une valeur (une variable ou le résultat d'une fonction qui renvoie une valeur) - dans ce cas, le compilateur n'a aucun doute sur la classe réelle de l'objet, et peut "hard-resolve" au moment de la compilation.
  • si la méthode virtuelle est déclarée final dans la classe à laquelle vous avez un pointeur ou une référence à travers laquelle vous l'appelez ( seulement en C++11 ). Dans ce cas, le compilateur sait que cette méthode ne peut pas subir d'autres primordial et il ne peut être que la méthode de cette classe.

Notez bien que les appels virtuels ont uniquement généraux de référence de deux pointeurs. Utiliser RTTI (bien que seulement disponible pour les classes polymorphiques) est plus lent que d'appeler des méthodes virtuelles, si vous trouvez un cas pour mettre en œuvre la même chose deux de ces façons. Par exemple, la définition de virtual bool HasHoof() { return false; } et ensuite la suppression seulement comme bool Horse::HasHoof() { return true; } vous fournirait la capacité pour appeler if (anim->HasHoof()) qui sera plus rapide que d'essayer if(dynamic_cast<Horse*>(anim)) . C'est parce que dynamic_cast doit marcher dans la hiérarchie de classe dans certains cas même récursivement pour voir s'il peut être construit le chemin à partir du type de pointeur réel et le type de classe désiré. Alors que l'appel virtuel est toujours le même - déréférencement de deux pointeurs.

2
répondu Ethouris 2015-04-14 12:29:41

chaque objet a un pointeur vtable qui pointe vers un tableau de fonctions de membre.

1
répondu 2008-09-19 03:33:03

quelque chose qui n'est pas mentionné ici dans toutes ces réponses est qu'en cas d'héritage multiple, où les classes de base ont toutes des méthodes virtuelles. La classe inheriting a plusieurs pointeurs vers un vmt. Le résultat est que la taille de chaque instance d'un tel objet est plus grand. Tout le monde sait qu'une classe avec des méthodes virtuelles a 4 octets supplémentaires pour le vmt, mais en cas d'héritage multiple c'est pour chaque classe de base qui a des méthodes virtuelles fois 4. 4 étant la taille du pointeur.

1
répondu Philip Stuyck 2015-04-12 19:54:54

voici un runnable mise en oeuvre manuelle de la table virtuelle en C++moderne. Il a une sémantique bien définie, Pas de hacks et pas de void* .

Note: .* et ->* sont des opérateurs différents de * et -> . Les indicateurs de fonction de membre fonctionnent différemment.

#include <iostream>
#include <vector>
#include <memory>

struct vtable; // forward declare, we need just name

class animal
{
public:
    const std::string& get_name() const { return name; }

    // these will be abstract
    bool has_tail() const;
    bool has_wings() const;
    void sound() const;

protected: // we do not want animals to be created directly
    animal(const vtable* vtable_ptr, std::string name)
    : vtable_ptr(vtable_ptr), name(std::move(name)) { }

private:
    friend vtable; // just in case for non-public methods

    const vtable* const vtable_ptr;
    std::string name;
};

class cat : public animal
{
public:
    cat(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does meow\n"; 
    }
};

class dog : public animal
{
public:
    dog(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does whoof\n"; 
    }
};

class parrot : public animal
{
public:
    parrot(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return false; }
    bool has_wings() const { return true; }
    void sound() const
    {
        std::cout << get_name() << " does crrra\n"; 
    }
};

// now the magic - pointers to member functions!
struct vtable
{
    bool (animal::* const has_tail)() const;
    bool (animal::* const has_wings)() const;
    void (animal::* const sound)() const;

    // constructor
    vtable (
        bool (animal::* const has_tail)() const,
        bool (animal::* const has_wings)() const,
        void (animal::* const sound)() const
    ) : has_tail(has_tail), has_wings(has_wings), sound(sound) { }
};

// global vtable objects
const vtable vtable_cat(
    static_cast<bool (animal::*)() const>(&cat::has_tail),
    static_cast<bool (animal::*)() const>(&cat::has_wings),
    static_cast<void (animal::*)() const>(&cat::sound));
const vtable vtable_dog(
    static_cast<bool (animal::*)() const>(&dog::has_tail),
    static_cast<bool (animal::*)() const>(&dog::has_wings),
    static_cast<void (animal::*)() const>(&dog::sound));
const vtable vtable_parrot(
    static_cast<bool (animal::*)() const>(&parrot::has_tail),
    static_cast<bool (animal::*)() const>(&parrot::has_wings),
    static_cast<void (animal::*)() const>(&parrot::sound));

// set vtable pointers in constructors
cat::cat(std::string name) : animal(&vtable_cat, std::move(name)) { }
dog::dog(std::string name) : animal(&vtable_dog, std::move(name)) { }
parrot::parrot(std::string name) : animal(&vtable_parrot, std::move(name)) { }

// implement dynamic dispatch
bool animal::has_tail() const
{
    return (this->*(vtable_ptr->has_tail))();
}

bool animal::has_wings() const
{
    return (this->*(vtable_ptr->has_wings))();
}

void animal::sound() const
{
    (this->*(vtable_ptr->sound))();
}

int main()
{
    std::vector<std::unique_ptr<animal>> animals;
    animals.push_back(std::make_unique<cat>("grumpy"));
    animals.push_back(std::make_unique<cat>("nyan"));
    animals.push_back(std::make_unique<dog>("doge"));
    animals.push_back(std::make_unique<parrot>("party"));

    for (const auto& a : animals)
        a->sound();

    // note: destructors are not dispatched virtually
}
1
répondu Xeverous 2018-07-26 14:50:33

Burly réponses sont correctes ici, sauf pour la question:

les classes abstraites ont-elles simplement un nul pour le pointeur de fonction d'au moins une entrée?

La réponse est qu'aucune table virtuelle est créée pour les classes abstraites. Il n'y a pas besoin puisque aucun objet de ces classes ne peut être créé!

En d'autres termes, si nous avons:

class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class

D* pD = new D();
B* pB = pD;

le pointeur vtbl le VTBL de la classe D sera accessible par pB. c'est exactement comme cela que le polymorphisme est mis en œuvre. C'est-à-dire comment les méthodes D sont accessibles par pB. Il n'est pas nécessaire d'avoir une vtbl pour la classe B.

en réponse au commentaire de Mike ci-dessous...

si la Classe B dans ma description a une méthode virtuelle foo () qui n'est pas dépassée par D et une méthode virtuelle bar() qui est dépassée, alors D vtbl aura un pointeur sur B foo () et sur son propre bar () . Il n'y a toujours pas de vtbl créée pour B.

0
répondu Andrew Stein 2008-09-19 05:08:17

preuve de concept très mignonne que j'ai faite un peu plus tôt(pour voir si l'ordre d'héritage compte); faites-moi savoir si votre implémentation de C++ la rejette réellement(ma version de gcc ne donne qu'un avertissement pour assigner des structures anonymes, mais c'est un bug), je suis curieux.

CCPolite.h :

#ifndef CCPOLITE_H
#define CCPOLITE_H

/* the vtable or interface */
typedef struct {
    void (*Greet)(void *);
    void (*Thank)(void *);
} ICCPolite;

/**
 * the actual "object" literal as C++ sees it; public variables be here too 
 * all CPolite objects use(are instances of) this struct's structure.
 */
typedef struct {
    ICCPolite *vtbl;
} CPolite;

#endif /* CCPOLITE_H */

CCPolite_constructor.h :

/** 
 * unconventionally include me after defining OBJECT_NAME to automate
 * static(allocation-less) construction.
 *
 * note: I assume CPOLITE_H is included; since if I use anonymous structs
 *     for each object, they become incompatible and cause compile time errors
 *     when trying to do stuff like assign, or pass functions.
 *     this is similar to how you can't pass void * to windows functions that
 *         take handles; these handles use anonymous structs to make 
 *         HWND/HANDLE/HINSTANCE/void*/etc not automatically convertible, and
 *         require a cast.
 */
#ifndef OBJECT_NAME
    #error CCPolite> constructor requires object name.
#endif

CPolite OBJECT_NAME = {
    &CCPolite_Vtbl
};

/* ensure no global scope pollution */
#undef OBJECT_NAME

principal.C :

#include <stdio.h>
#include "CCPolite.h"

// | A Greeter is capable of greeting; nothing else.
struct IGreeter
{
    virtual void Greet() = 0;
};

// | A Thanker is capable of thanking; nothing else.
struct IThanker
{
    virtual void Thank() = 0;
};

// | A Polite is something that implements both IGreeter and IThanker
// | Note that order of implementation DOES MATTER.
struct IPolite1 : public IGreeter, public IThanker{};
struct IPolite2 : public IThanker, public IGreeter{};

// | implementation if IPolite1; implements IGreeter BEFORE IThanker
struct CPolite1 : public IPolite1
{
    void Greet()
    {
        puts("hello!");
    }

    void Thank()
    {
        puts("thank you!");
    }
};

// | implementation if IPolite1; implements IThanker BEFORE IGreeter
struct CPolite2 : public IPolite2
{
    void Greet()
    {
        puts("hi!");
    }

    void Thank()
    {
        puts("ty!");
    }
};

// | imposter Polite's Greet implementation.
static void CCPolite_Greet(void *)
{
    puts("HI I AM C!!!!");
}

// | imposter Polite's Thank implementation.
static void CCPolite_Thank(void *)
{
    puts("THANK YOU, I AM C!!");
}

// | vtable of the imposter Polite.
ICCPolite CCPolite_Vtbl = {
    CCPolite_Thank,
    CCPolite_Greet    
};

CPolite CCPoliteObj = {
    &CCPolite_Vtbl
};

int main(int argc, char **argv)
{
    puts("\npart 1");
    CPolite1 o1;
    o1.Greet();
    o1.Thank();

    puts("\npart 2");    
    CPolite2 o2;    
    o2.Greet();
    o2.Thank();    

    puts("\npart 3");    
    CPolite1 *not1 = (CPolite1 *)&o2;
    CPolite2 *not2 = (CPolite2 *)&o1;
    not1->Greet();
    not1->Thank();
    not2->Greet();
    not2->Thank();

    puts("\npart 4");        
    CPolite1 *fake = (CPolite1 *)&CCPoliteObj;
    fake->Thank();
    fake->Greet();

    puts("\npart 5");        
    CPolite2 *fake2 = (CPolite2 *)fake;
    fake2->Thank();
    fake2->Greet();

    puts("\npart 6");        
    #define OBJECT_NAME fake3
    #include "CCPolite_constructor.h"
    fake = (CPolite1 *)&fake3;
    fake->Thank();
    fake->Greet();

    puts("\npart 7");        
    #define OBJECT_NAME fake4
    #include "CCPolite_constructor.h"
    fake2 = (CPolite2 *)&fake4;
    fake2->Thank();
    fake2->Greet();    

    return 0;
}

sortie:

part 1
hello!
thank you!

part 2
hi!
ty!

part 3
ty!
hi!
thank you!
hello!

part 4
HI I AM C!!!!
THANK YOU, I AM C!!

part 5
THANK YOU, I AM C!!
HI I AM C!!!!

part 6
HI I AM C!!!!
THANK YOU, I AM C!!

part 7
THANK YOU, I AM C!!
HI I AM C!!!!

note Comme je n'attribue jamais mon objet fictif, il n'y a pas besoin de faire de destruction; Les Destructeurs sont automatiquement mis à la fin de la portée des objets alloués dynamiquement pour récupérer la mémoire de l'objet littéral lui-même et le pointeur vtable.

0
répondu Dmitry 2017-01-18 04:22:34