Frage

Wir alle wissen, was virtuelle Funktionen sind in C ++, aber wie sind sie auf einer tiefen Ebene umgesetzt?

Kann die VTable modifiziert werden oder auch direkt zur Laufzeit zugegriffen?

Gibt es den VTable für alle Klassen, oder nur diejenigen, die mindestens eine virtuelle Funktion haben?

Sie abstrakte Klassen einfach ein NULL für den Funktionszeiger von mindestens einem Eintrag?

Gibt es eine einzelne virtuelle Funktion, die ganze Klasse verlangsamen zu müssen? Oder nur der Aufruf der Funktion, die virtuell ist? Und ist die Geschwindigkeit betroffene bekommen, wenn die virtuelle Funktion tatsächlich überschrieben wird oder nicht, oder dies keine Auswirkung hat, solange es virtuell ist.

War es hilfreich?

Lösung

Wie werden virtuelle Funktionen auf einer tiefen Ebene umgesetzt?

"Virtuelle Funktionen in C ++" :

  

Jedes Mal, wenn ein Programm eine virtuelle Funktion hat erklärt, ein V - Tabelle ist für die Klasse konstruiert. Die V-Tabelle besteht aus Adressen für die virtuellen Funktionen für Klassen, die eine oder mehr virtuelle Funktionen enthalten. Das Objekt der Klasse, die virtuelle Funktion enthält, enthält einen virtuellen Zeiger, den Speicher auf die Basisadresse der virtuellen Tabelle verweist. Wann immer es ein virtueller Funktionsaufruf ist, wird die V-Tabelle verwendet, um die Funktion Adresse aufzulösen. Ein Objekt der Klasse, die eine oder mehr virtuelle Funktionen enthält enthält einen virtuellen Zeiger die vptr ganz am Anfang des Objekts in dem Speicher abgerufen. Daraus ergibt sich die Größe des Objekts, in diesem Fall erhöht sich durch die Größe des Zeigers. Diese vptr enthält die Basisadresse der virtuellen Tabelle im Speicher. Beachten Sie, dass virtuelle Tabellen sind klassenspezifische, das heißt, es gibt nur eine virtuelle Tabelle für eine Klasse ist, unabhängig von der Anzahl der virtuellen Funktionen enthält. Diese virtuelle Tabelle enthält wiederum die Basisadressen von einem oder mehreren virtuellen Funktionen der Klasse. Zu dem Zeitpunkt, wenn eine virtuelle Funktion auf einem Objekt aufgerufen wird, stellt die vptr dieses Objekt die Basisadresse der virtuellen Tabelle für diese Klasse im Speicher. Diese Tabelle wird verwendet, um den Funktionsaufruf zu lösen, wie es die Adressen aller virtuellen Funktionen dieser Klasse enthält. Dies ist, wie die dynamische Bindung während eines virtuellen Funktionsaufrufes aufgelöst wird.

Kann das Vtable geändert oder sogar direkt zur Laufzeit zugegriffen?

Universell, ich glaube, die Antwort ist „Nein“. Sie könnten etwas Speicher tun Mangeln die VTable zu finden, aber würden Sie wissen noch nicht, was die Funktion Signatur sieht aus wie es zu nennen. Alles, was Sie würde mit dieser Fähigkeit erreichen will (also die Sprache unterstützt) soll möglich sein, ohne Zugriff auf die VTable direkt oder zur Laufzeit zu ändern. Beachten Sie auch, die Sprache C ++ spec nicht festlegen, dass vtables erforderlich sind, -. Aber das ist, wie die meisten Compiler implementieren virtuelle Funktionen

Ist die V-Tabelle für alle Objekte vorhanden sind, oder nur diejenigen, die mindestens eine virtuelle Funktion haben?

I glauben die Antwort hier ist „es auf die Umsetzung hängt“, da die Spezifikation nicht vtables in erster Linie benötigen. In der Praxis jedoch, glaube ich, alle modernen Compiler nur eine V-Tabelle erstellen, wenn eine Klasse mindestens 1 virtuelle Funktion hat. Es gibt einen Raum Overhead mit der V-Tabelle und einem Zeitaufwand verbunden sind, die mit einer virtuellen Funktion vs einer nicht-virtuellen Funktion aufrufen.

abstrakte Klassen Sie einfach ein NULL für den Funktionszeiger von mindestens einem Eintrag?

Die Antwort ist es durch die Sprache spec nicht spezifiziert ist, so dass es bei der Umsetzung abhängt. Der Aufruf der rein virtuelle Funktion führt zu undefiniertem Verhalten, wenn es nicht definiert ist (was es in der Regel nicht) (ISO / IEC 14882: 2003 10,4-2). In der Praxis hat es einen Schlitz in der VTable für die Funktion zuzuordnen, sondern eine Adresse, um es nicht zu. Dies lässt die VTable unvollständig, das die abgeleiteten Klassen erfordert die Funktion und füllen Sie das Vtable zu implementieren. Einige Implementierungen stellen Sie einfach einen NULL-Zeiger in der VTable-Eintrag; andere Implementierungen stellen einen Zeiger auf eine Dummy-Methode, die etwas Ähnliches wie eine Behauptung der Fall ist.

Beachten Sie, dass eine abstrakte Klasse eine Implementierung für eine rein virtuelle Funktion definieren kann, aber diese Funktion kann nur mit einer qualifizierten Nummer Syntax (dh., Vollständig unter Angabe die Klasse in dem Methodennamen, ähnlich wie bei Aufruf eine Basisklasse aufgerufen werden Verfahren aus einer abgeleiteten Klasse). Dies geschieht, eine einfach zu bieten Standardimplementierung zu verwenden, während nach wie vor erforderlich ist, dass eine abgeleitete Klasse eine Überschreibung bereitzustellen.

Gibt es eine einzelne virtuelle Funktion, die ganze Klasse langsam mit oder nur den Aufruf der Funktion, die virtuell ist?

Dies wird an den Rand meines Wissens immer, so jemand mir hier helfen, wenn ich falsch!

I glaubt , dass nur die Funktionen, die in der Klasse Erfahrung eine virtuelle Funktion im Vergleich zu einer nicht-virtuellen Funktion der Zeit Performance-Einbußen virtuell sind im Zusammenhang zu nennen. Der Raum Overhead für die Klasse ist es so oder so. Beachten Sie, dass, wenn es eine VTable ist, gibt es nur 1 pro Klasse , nicht eine pro Objekt .

Ist die Geschwindigkeit betroffen erhalten, wenn die virtuelle Funktion tatsächlich außer Kraft gesetzt wird oder nicht, oder dies keine Auswirkung hat, solange es virtuell ist?

Ich glaube nicht, die Ausführungszeit einer virtuellen Funktion, verringert sich im Vergleich zu den Aufruf der Basis virtuelle Funktion außer Kraft gesetzt wird. Es besteht jedoch ein zusätzlicher Raum Overhead für die Klasse im Zusammenhang mit einem anderen VTable für die abgeleitete Klasse vs der Basisklasse definiert wird.

Weitere Ressourcen:

http : //www.codersource.net/published/view/325/virtual_functions_in.aspx (über Maschinen Weg zurück)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/ abi.html # vTable

Andere Tipps

  • Kann das Vtable geändert oder sogar direkt zugegriffen zur Laufzeit?

Nicht portabel, aber wenn es Ihnen nichts ausmacht schmutzigen Tricks, sicher!

  

ACHTUNG: : Diese Technik wird nicht von Kindern für den Einsatz empfohlen, Erwachsene im Alter von 969 oder kleine pelzige Kreaturen aus Alpha Centauri. Nebenwirkungen können Dämonen, die aus der Nase fliegen, die plötzliche Erscheinung von Yog-Sothoth als erforderlichen Genehmiger auf all nachfolgenden Code-Reviews oder die rückwirkende Zusatz von IHuman::PlayPiano() an alle bestehenden Instanzen]

In den meisten Compilern ich gesehen habe, ist die vtbl * das erste 4 Bytes des Objekts und die vtbl Inhalte sind einfach eine Reihe von Mitglied Zeigern dort (in der Regel in der Reihenfolge, wie sie erklärt wurden, mit der Basisklasse zuerst) . Es gibt natürlich auch andere mögliche Layouts, aber das ist, was ich in der Regel beobachtet haben.

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;

Jetzt ein paar Spielereien ziehen ...

Ändern Klasse zur Laufzeit:

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

ein Verfahren für alle Instanzen austauschen (monkeypatching eine Klasse)

Das hier ist ein wenig komplizierter, da die vtbl sich wahrscheinlich in Nur-Lese-Speicher ist.

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

Letzteres ist eher wahrscheinlich Virus-Checker und den Link aufwachen und beachten Sie, aufgrund der mprotect Manipulationen zu machen. In einem Verfahren der NX-Bit es auch fehlschlagen.

Gibt es eine einzelne virtuelle Funktion, die ganze Klasse langsam mit nach unten?

  

oder nur der Aufruf der Funktion, die virtuell ist? Und ist die Geschwindigkeit betroffene bekommen, wenn die virtuelle Funktion tatsächlich überschrieben wird oder nicht, oder dies keine Auswirkung hat, solange es virtuell ist.

verlangsamt die ganze Klasse virtuelle Funktionen zu haben, sofern ein weiteres Element von Daten muss initialisiert werden, kopiert, ... wenn sie mit einem Objekt einer solchen Klasse zu tun. Für eine Klasse mit einem halben Dutzend Mitgliedern oder so, soll der Unterschied vernachlässigbar sein. Für eine Klasse, die nur ein einziges char Mitglied enthält, oder keine Mitglieder überhaupt, könnte der Unterschied bemerkenswert sein.

Abgesehen davon, ist es wichtig zu beachten, dass jeder Anruf nicht zu einer virtuellen Funktion ein virtueller Funktionsaufruf ist. Wenn Sie ein Objekt eines bekannten Typs haben, kann der Compiler Code für einen normalen Funktionsaufruf emittieren, und kann sogar inline die Funktion, wenn es, wie es sich anfühlt. Es ist nur, wenn Sie polymorphe Anrufe zu tun, über einen Zeiger oder eine Referenz, die auf ein Objekt der Basisklasse oder an einem Objekt von einigen abgeleiteten Klasse verweisen könnte, dass Sie die VTable indirection brauchen und für sie in Bezug auf die Leistung zahlen.

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
}

Die Schritte der Hardware zu nehmen hat im Wesentlichen die gleichen, egal, ob die Funktion überschrieben wird oder nicht. Die Adresse des vtable ist von dem Objekt zu lesen, Zeigern der Funktion aus dem entsprechenden Steckplatz abgerufen, und die durch die Zeiger aufgerufenen Funktion. In Bezug auf die tatsächliche Leistung, Verzweigungsvorhersagen könnte einen gewissen Einfluss haben. So zum Beispiel, wenn die meisten Ihrer Objekte auf die gleiche Implementierung einer virtuellen Funktion, dann gibt es eine gewisse Chance, dass der Verzweigungsprädiktor korrekt vorhersagen, welche schon vor dem Zeiger abgerufen wurde aufrufe funktionieren. Aber es ist egal, welche Funktion ist die eines gemeinsam:. Die meisten Objekte in dem nicht-überschrieben Basisfall delegieren könnte, oder die meisten Objekte zu der gleichen Unterklasse gehört, und damit auf den gleichen überschrieben Fall delegieren

Wie sind sie auf einer tiefen Ebene umgesetzt?

Ich mag die Idee von jheriko zu demonstrieren dies eine Mock-Implementierung verwendet wird. Aber ich würde verwenden C über so etwas wie den Code zu implementieren, so dass das niedrige Niveau mehr ist leicht zu sehen.

parent class 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!
}

abgeleiteten Klasse 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
}

Funktion f virtuellen Funktionsaufruf durchführt

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);
}

So Sie sehen können, eine V-Tabelle ist nur ein statischer Block im Speicher, meist Funktionszeiger enthält. Jedes Objekt einer polymorphen Klasse wird auf die VTable weisen auf ihren dynamischen Typ entspricht. Dies macht auch die Verbindung zwischen RTTI und virtuellen Funktionen klarer: Sie können überprüfen, welche Art eine Klasse ist einfach durch einen Blick auf, was Vtable es zeigt auf. Das Vorstehende ist in vielerlei Hinsicht vereinfacht, wie z.B. Mehrfachvererbung, aber das allgemeine Konzept ist Klang.

Wenn arg vom Typ Foo* ist und Sie arg->vtable nehmen, aber es ist tatsächlich ein Objekt vom Typ Bar, dann erhalten Sie noch die richtige Adresse des vtable. Das ist, weil der vtable an der Adresse des Objekts immer das erste Element ist, egal ob es vtable oder base.vtable in einem korrekt typisierte Ausdruck genannt wird.

In der Regel mit einer VTable, ein Array von Zeigern auf Funktionen.

Diese Antwort wurde bisher in die Community Wiki Antwort

  • Sie abstrakte Klassen einfach eine NULL für den Funktionszeiger von mindestens einem Eintrag haben?

Die Antwort darauf ist, dass es nicht spezifiziert ist - die rein virtuelle Funktion führt zu undefinierten Verhalten aufrufen, wenn es nicht definiert ist (was es in der Regel nicht) (ISO / IEC 14882: 2003 10,4-2). Einige Implementierungen stellen Sie einfach einen NULL-Zeiger in der VTable-Eintrag; andere Implementierungen stellen einen Zeiger auf eine Dummy-Methode, die etwas Ähnliches wie eine Behauptung der Fall ist.

Beachten Sie, dass eine abstrakte Klasse eine Implementierung für eine rein virtuelle Funktion definieren kann, aber diese Funktion kann nur mit einer qualifizierten Nummer Syntax (dh., Vollständig unter Angabe die Klasse in dem Methodennamen, ähnlich wie bei Aufruf eine Basisklasse aufgerufen werden Verfahren aus einer abgeleiteten Klasse). Dies geschieht, eine einfach zu bieten Standardimplementierung zu verwenden, während nach wie vor erforderlich ist, dass eine abgeleitete Klasse eine Überschreibung bereitzustellen.

Sie können die Funktionalität der virtuellen Funktionen in C ++ unter Verwendung von Funktionszeigern als Mitglieder einer Klasse und statische Funktionen wie die Implementierungen neu erstellen oder mit Zeigern auf Elementfunktionen und Elementfunktionen für die Implementierungen. Es gibt nur Notations Vorteile zwischen den beiden Methoden ... in der Tat virtuelle Funktionsaufrufe sind nur Notations Bequemlichkeit selbst. In der Tat Vererbung ist nur eine Notations Bequemlichkeit ... es kann alles ohne Verwendung der Sprachfunktionen für die Vererbung implementiert werden. :)

Das unten ist nicht getestet Mist, wahrscheinlich fehlerhaften Code, aber hoffentlich zeigt die Idee.

z.

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; }
...
};

Ich werde versuchen, es einfach zu machen:)

Wir alle wissen, was virtuelle Funktionen sind in C ++, aber wie sind sie auf einer tiefen Ebene umgesetzt?

Dies ist ein Array mit Zeigern auf Funktionen, die Implementierungen einer bestimmten virtuellen Funktion sind. Ein Index in diesem Array repräsentiert bestimmten Index einer virtuellen Funktion für eine Klasse definiert. Dazu gehört rein virtuelle Funktionen.

Wenn eine polymorphe Klasse von einer anderen polymorphen Klasse abgeleitet wird, können wir die folgenden Situationen haben:

  • Die Berechnungs Klasse keine neue virtuelle Funktionen hinzufügen, noch überschreibt alle. In diesem Fall wird diese Klasse teilt die V-Tabelle mit der Basisklasse.
  • Die Berechnungs Klasse fügt und überschreibt virtuelle Methoden. In diesem Fall wird es eine eigene VTable, wo die zusätzlichen virtuellen Funktionen Index begonnen haben Vergangenheit die letzte abgeleitet.
  • Mehrere polymorphen Klassen in der Vererbung. In diesem Fall haben wir einen Index-Verschiebung zwischen dem zweiten und nächsten Basen und dem Index in der abgeleiteten Klasse

Kann das Vtable geändert oder sogar direkt zur Laufzeit zugegriffen?

Nicht-Standard Art und Weise - es gibt keine API auf sie zuzugreifen. Compiler kann einige Erweiterungen oder private APIs müssen auf sie zugreifen, aber das kann nur eine Erweiterung sein.

Ist die V-Tabelle für alle Klassen existieren, oder nur diejenigen, die mindestens eine virtuelle Funktion haben?

Nur diejenigen, die mindestens eine virtuelle Funktion (sei es auch destructor) oder ableiten mindestens eine Klasse, die seine VTable hat ( „polymorph“).

abstrakte Klassen Sie einfach ein NULL für den Funktionszeiger von mindestens einem Eintrag?

Das ist eine mögliche Implementierung, sondern eher nicht praktiziert. Stattdessen gibt es in der Regel eine Funktion, die so etwas wie „rein virtuelle Funktion namens“ druckt und nicht abort(). Der Aufruf an die auftreten können, wenn Sie versuchen, die abstrakte Methode im Konstruktor oder Destruktor aufrufen.

Gibt es eine einzelne virtuelle Funktion, die ganze Klasse verlangsamen zu müssen? Oder nur der Aufruf der Funktion, die virtuell ist? Und ist die Geschwindigkeit betroffen erhalten, wenn die virtuelle Funktion tatsächlich überschrieben wird oder nicht, oder dies keine Auswirkung hat, solange es virtuell ist.

Die Abschwächung ist nur abhängig davon, ob der Anruf als direkter Anruf gelöst wird oder als virtueller Anruf. Und nichts anderes zählt. :)

Wenn Sie eine virtuelle Funktion über einen Zeiger oder Verweis auf ein Objekt aufrufen, dann wird es immer als virtuelle Call implementiert werden - weil der Compiler kann nie wissen, welche Art von Objekt zu diesem Zeiger in Laufzeit zugeordnet werden, und ob es in denen eine Klasse dieses Verfahren außer Kraft gesetzt wird oder nicht. Nur in zwei Fällen der Compiler den Aufruf einer virtuellen Funktion als direkte Aufruf auflösen kann:

  • Wenn Sie die Methode durch einen Wert (eine Variable oder das Ergebnis einer Funktion, die einen Wert zurückgibt) nennen - in diesem Fall der Compiler keine Zweifel hat, was die tatsächliche Klasse des Objekts ist, und kann „hart-resolve“ es bei der Kompilierung.
  • Wenn die virtuelle Methode in der Klasse deklariert final zu dem man einen Zeiger oder eine Referenz, durch die man es nennen ( nur in C ++ 11 ). In diesem Fall weiß Compiler, dass diese Methode nicht weiter überwiegende laufen kann, und es kann nur die Methode von dieser Klasse sein.

Beachten Sie aber, dass virtuelle Anrufe nur Overhead von dereferencing zwei Zeiger. Mit RTTI (obwohl nur für polymorphe Klassen) ist langsamer als virtuelle Methoden aufrufen, sollten Sie einen Fall finden die gleiche Sache zwei solche Möglichkeiten zu implementieren. Zum Beispiel definiert virtual bool HasHoof() { return false; } und dann nur außer Kraft setzen, wie bool Horse::HasHoof() { return true; } Sie mit der Fähigkeit bereitstellen würde if (anim->HasHoof()) zu nennen, die schneller als zu versuchen, if(dynamic_cast<Horse*>(anim)) sein werden. Dies liegt daran, dynamic_cast durch die Klassenhierarchie in einigen Fällen zu gehen hat auch rekursiv, wenn es sehen kann den Pfad vom aktuellen Zeigertyp und der gewünschten Klasse Typ gebaut werden. während thvirtuelle Call-e ist immer das gleiche -. dereferencing zwei Zeiger

Hier ist eine runnable manuelle Implementierung von virtueller Tabelle in der modernen C ++. Es verfügt über gut definierte Semantik, keine Hacks und keine void*.

Hinweis: .* und ->* sind verschiedene Operatoren als * und ->. Mitglied Funktionszeiger funktionieren anders.

#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
}

Jedes Objekt hat einen VTable-Zeiger, der auf ein Array von Elementfunktionen zeigt.

Etwas hier nicht in all diesen Antworten erwähnt ist, dass im Fall von Mehrfachvererbung, wo die Basisklassen all virtuelle Methoden. Die Vererbungs Klasse hat mehrere Zeiger auf eine vmt. Das Ergebnis ist, dass die Größe jeder Instanz eines solchen Objekts ist größer. Jeder weiß, dass eine Klasse mit virtuellen Methoden hat 4 Byte extra für das vmt, aber im Fall von Mehrfachvererbung ist es für jede Basisklasse, die virtuellen Methoden Zeiten 4. 4 sind die Größe des Zeigers hat.

Kräftige die Antworten richtig sind hier bis auf die Frage:

Sie abstrakte Klassen einfach ein NULL für den Funktionszeiger von mindestens einem Eintrag haben?

Die Antwort ist, dass keine virtuelle Tabelle für abstrakte Klassen überhaupt entsteht. Es besteht keine Notwendigkeit, da keine Objekte dieser Klassen erstellt werden können!

Mit anderen Worten, wenn wir haben:

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

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

Der vtbl Zeiger durch pB zugegriffen wird die vtbl der Klasse D. sein Dieses ist genau, wie Polymorphismus implementiert ist. Das heißt, wie D Methoden durch pB zugegriffen wird. Es besteht keine Notwendigkeit für eine vtbl für Klasse B.

Als Antwort auf Mikes Kommentar unten ...

Wenn die B-Klasse in meiner Beschreibung hat eine virtuelle Methode foo () , die nicht von D und einer virtuellen Methode überschrieben wird bar () , die außer Kraft gesetzt wird, dann D's vtbl einen Zeiger auf B hat foo () und auf seinen eigenen bar () . Es gibt noch keine vtbl für B. erstellt

sehr nett Proof of Concept i etwas früher gemacht (um zu sehen, ob die Reihenfolge der inheritence Angelegenheiten); lassen Sie mich wissen, ob Ihre Implementierung von C ++ eigentlich ablehnt (meine Version von gcc nur eine Warnung für die Zuweisung von anonymen Strukturen gibt, aber das ist ein Fehler), ich bin neugierig.

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

main.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;
}

Ausgabe:

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!!!!

Notiz, da ich nie bin mein gefälschtes Objekt Zuteilung, gibt es keine Notwendigkeit, jede Zerstörung zu tun; Destruktoren werden am Ende des Anwendungsbereichs der dynamisch zugewiesenen Objekte automatisch setzen die Erinnerung an die Objektliteral selbst und die VTable-Zeiger zurückzufordern.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top