Pregunta

Todos sabemos qué funciones virtuales hay en C++, pero ¿cómo se implementan a un nivel profundo?

¿Se puede modificar la vtable o incluso acceder directamente a ella en tiempo de ejecución?

¿Existe la vtable para todas las clases, o solo para aquellas que tienen al menos una función virtual?

¿Las clases abstractas simplemente tienen un NULL para el puntero de función de al menos una entrada?

¿Tener una única función virtual ralentiza toda la clase?¿O solo la llamada a la función que es virtual?¿Y la velocidad se ve afectada si la función virtual se sobrescribe o no, o esto no tiene ningún efecto mientras sea virtual?

¿Fue útil?

Solución

¿Cómo se implementan las funciones virtuales a un nivel profundo?

De "Funciones virtuales en C++":

Siempre que un programa tiene una función virtual declarada, se construye una tabla v para la clase.La tabla v consta de direcciones a las funciones virtuales para clases que contienen una o más funciones virtuales.El objeto de la clase que contiene la función virtual contiene un puntero virtual que apunta a la dirección base de la tabla virtual en la memoria.Siempre que hay una llamada a una función virtual, la tabla v se utiliza para resolver la dirección de la función.Un objeto de la clase que contiene una o más funciones virtuales contiene un puntero virtual llamado vptr al comienzo del objeto en la memoria.Por lo tanto, el tamaño del objeto en este caso aumenta según el tamaño del puntero.Este vptr contiene la dirección base de la tabla virtual en la memoria.Tenga en cuenta que las tablas virtuales son específicas de cada clase, es decir, solo hay una tabla virtual para una clase independientemente del número de funciones virtuales que contenga.Esta tabla virtual a su vez contiene las direcciones base de una o más funciones virtuales de la clase.En el momento en que se llama a una función virtual en un objeto, el vptr de ese objeto proporciona la dirección base de la tabla virtual para esa clase en la memoria.Esta tabla se utiliza para resolver la llamada a la función, ya que contiene las direcciones de todas las funciones virtuales de esa clase.Así es como se resuelve el enlace dinámico durante una llamada a una función virtual.

¿Se puede modificar la vtable o incluso acceder directamente a ella en tiempo de ejecución?

Universalmente creo que la respuesta es "no".Podrías alterar un poco la memoria para encontrar la vtable, pero aún no sabrías cómo se ve la firma de la función para llamarla.Cualquier cosa que desee lograr con esta capacidad (que el lenguaje admita) debería ser posible sin acceder directamente a la vtable o modificarla en tiempo de ejecución.También tenga en cuenta la especificación del lenguaje C++. no es especifique que se requieren vtables; sin embargo, así es como la mayoría de los compiladores implementan funciones virtuales.

¿Existe la vtable para todos los objetos o solo para aquellos que tienen al menos una función virtual?

I creer la respuesta aquí es "depende de la implementación", ya que la especificación no requiere vtables en primer lugar.Sin embargo, en la práctica, creo que todos los compiladores modernos solo crean una tabla virtual si una clase tiene al menos una función virtual.Hay una sobrecarga de espacio asociada con vtable y una sobrecarga de tiempo asociada con la llamada de una función virtual frente a una función no virtual.

¿Las clases abstractas simplemente tienen un NULL para el puntero de función de al menos una entrada?

La respuesta es que no está especificado en la especificación del idioma, por lo que depende de la implementación.Llamar a la función virtual pura da como resultado un comportamiento indefinido si no está definida (que normalmente no lo está) (ISO/IEC 14882:2003 10.4-2).En la práctica, asigna una ranura en vtable para la función pero no le asigna una dirección.Esto deja la vtable incompleta, lo que requiere que las clases derivadas implementen la función y completen la vtable.Algunas implementaciones simplemente colocan un puntero NULL en la entrada de vtable;otras implementaciones colocan un puntero a un método ficticio que hace algo similar a una afirmación.

Tenga en cuenta que una clase abstracta puede definir una implementación para una función virtual pura, pero esa función solo se puede llamar con una sintaxis de identificación calificada (es decir, especificando completamente la clase en el nombre del método, similar a llamar a un método de clase base desde un clase derivada).Esto se hace para proporcionar una implementación predeterminada fácil de usar, sin dejar de requerir que una clase derivada proporcione una anulación.

¿Tener una sola función virtual ralentiza toda la clase o solo la llamada a la función que es virtual?

Esto está llegando al límite de mis conocimientos, ¡así que alguien, por favor, ayúdeme si me equivoco!

I creer que solo las funciones que son virtuales en la clase experimentan el impacto en el rendimiento de tiempo relacionado con llamar a una función virtual vs.una función no virtual.El espacio superior para la clase está ahí de cualquier manera.Tenga en cuenta que si hay una vtable, solo hay 1 por clase, ni uno por objeto.

¿La velocidad se ve afectada si la función virtual se anula o no, o esto no tiene ningún efecto mientras sea virtual?

No creo que el tiempo de ejecución de una función virtual anulada disminuya en comparación con llamar a la función virtual base.Sin embargo, hay una sobrecarga de espacio adicional para la clase asociada con la definición de otra tabla virtual para la clase derivada frente a la clase base.

Recursos adicionales:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (a través de la máquina de regreso)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable

Otros consejos

  • ¿Se puede modificar la vtable o incluso acceder directamente a ella en tiempo de ejecución?

No de forma portátil, pero si no te importan los trucos sucios, ¡seguro!

ADVERTENCIA:Esta técnica no se recomienda para niños, adultos menores de 969, o pequeñas criaturas peludas de Alpha Centauri.Los efectos secundarios pueden incluir demonios que salen volando de tu nariz, la aparición abrupta de Yog Sothoth como aprobador requerido en todas las revisiones de código posteriores, o la adición retroactiva de IHuman::PlayPiano() a todas las instancias existentes]

En la mayoría de los compiladores que he visto, vtbl * son los primeros 4 bytes del objeto, y el contenido de vtbl es simplemente una matriz de punteros de miembros allí (generalmente en el orden en que fueron declarados, con el primero de la clase base).Por supuesto, existen otros diseños posibles, pero eso es lo que he observado en general.

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;

Ahora a hacer algunas travesuras...

Cambiar de clase en tiempo de ejecución:

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

Reemplazar un método para todas las instancias (monkeypatching una clase)

Esto es un poco más complicado, ya que el vtbl en sí probablemente esté en la memoria de solo lectura.

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

Es bastante probable que esto último haga que los detectores de virus y el enlace se activen y se den cuenta, debido a las manipulaciones de mprotect.En un proceso que utiliza el bit NX, es posible que falle.

¿Tener una única función virtual ralentiza toda la clase?

¿O solo la llamada a la función que es virtual?¿Y la velocidad se ve afectada si la función virtual se sobrescribe o no, o esto no tiene ningún efecto mientras sea virtual?

Tener funciones virtuales ralentiza toda la clase en la medida en que es necesario inicializar, copiar,… un elemento más de datos cuando se trata de un objeto de dicha clase.Para una clase con aproximadamente media docena de miembros, la diferencia debería ser insignificante.Para una clase que sólo contiene un único char miembro o ningún miembro, la diferencia puede ser notable.

Aparte de eso, es importante tener en cuenta que no todas las llamadas a una función virtual son llamadas a una función virtual.Si tiene un objeto de un tipo conocido, el compilador puede emitir código para una invocación de función normal, e incluso puede insertar dicha función si así lo desea.Solo cuando realiza llamadas polimórficas, a través de un puntero o referencia que podría apuntar a un objeto de la clase base o a un objeto de alguna clase derivada, necesita la dirección indirecta de vtable y paga por ella en términos de rendimiento.

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
}

Los pasos que debe seguir el hardware son esencialmente los mismos, sin importar si la función se sobrescribe o no.La dirección de la vtable se lee del objeto, el puntero de función se recupera de la ranura apropiada y la función se llama mediante el puntero.En términos de rendimiento real, las predicciones de sucursales pueden tener algún impacto.Entonces, por ejemplo, si la mayoría de sus objetos se refieren a la misma implementación de una función virtual determinada, entonces existe alguna posibilidad de que el predictor de rama prediga correctamente qué función llamar incluso antes de que se haya recuperado el puntero.Pero no importa cuál función sea la común:podrían ser la mayoría de los objetos que se delegan al caso base no sobrescrito, o la mayoría de los objetos que pertenecen a la misma subclase y, por lo tanto, se delegan al mismo caso sobrescrito.

¿Cómo se implementan a un nivel profundo?

Me gusta la idea de jheriko de demostrar esto mediante una implementación simulada.Pero usaría C para implementar algo parecido al código anterior, de modo que el nivel bajo se vea más fácilmente.

clase padre 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!
}

barra de clase derivada

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
}

función f realizando llamada a función virtual

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

Como puede ver, una vtable es solo un bloque estático en la memoria, que en su mayoría contiene punteros de función.Cada objeto de una clase polimórfica apuntará a la vtable correspondiente a su tipo dinámico.Esto también aclara la conexión entre RTTI y las funciones virtuales:puede comprobar de qué tipo es una clase simplemente mirando a qué vtable apunta.Lo anterior se simplifica de muchas maneras, como p.e.herencia múltiple, pero el concepto general es sólido.

Si arg es de tipo Foo* y tomas arg->vtable, pero en realidad es un objeto de tipo Bar, entonces aún obtendrás la dirección correcta del vtable.Eso es porque el vtable es siempre el primer elemento en la dirección del objeto, sin importar si se llama vtable o base.vtable en una expresión escrita correctamente.

Generalmente con una VTable, una serie de punteros a funciones.

Esta respuesta ha sido incorporada al Respuesta de la comunidad Wiki

  • ¿Las clases abstractas simplemente tienen un NULL para el puntero de función de al menos una entrada?

La respuesta es que no está especificada: llamar a la función virtual pura da como resultado un comportamiento indefinido si no está definida (lo cual generalmente no lo está) (ISO/IEC 14882:2003 10.4-2).Algunas implementaciones simplemente colocan un puntero NULL en la entrada de vtable;otras implementaciones colocan un puntero a un método ficticio que hace algo similar a una afirmación.

Tenga en cuenta que una clase abstracta puede definir una implementación para una función virtual pura, pero esa función solo se puede llamar con una sintaxis de identificación calificada (es decir, especificando completamente la clase en el nombre del método, similar a llamar a un método de clase base desde un clase derivada).Esto se hace para proporcionar una implementación predeterminada fácil de usar, sin dejar de requerir que una clase derivada proporcione una anulación.

Puede recrear la funcionalidad de funciones virtuales en C++ usando punteros de función como miembros de una clase y funciones estáticas como implementaciones, o usando punteros a funciones miembro y funciones miembro para las implementaciones.Sólo hay ventajas de notación entre los dos métodos...de hecho, las llamadas a funciones virtuales son en sí mismas solo una conveniencia de notación.De hecho, la herencia es sólo una conveniencia notacional...todo se puede implementar sin utilizar las funciones del lenguaje para la herencia.:)

Lo siguiente es un código basura no probado, probablemente con errores, pero con suerte demuestra la idea.

p.ej.

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

Intentaré hacerlo simple :)

Todos sabemos qué funciones virtuales hay en C++, pero ¿cómo se implementan a un nivel profundo?

Se trata de una matriz con punteros a funciones, que son implementaciones de una función virtual particular.Un índice en esta matriz representa un índice particular de una función virtual definida para una clase.Esto incluye funciones virtuales puras.

Cuando una clase polimórfica deriva de otra clase polimórfica podemos tener las siguientes situaciones:

  • La clase derivada no agrega nuevas funciones virtuales ni anula ninguna.En este caso, esta clase comparte la vtable con la clase base.
  • La clase derivada agrega y anula métodos virtuales.En este caso, obtiene su propia tabla virtual, donde las funciones virtuales agregadas tienen un índice que comienza después del último derivado.
  • Múltiples clases polimórficas en la herencia.En este caso tenemos un cambio de índice entre la segunda y la siguiente base y su índice en la clase derivada.

¿Se puede modificar la vtable o incluso acceder directamente a ella en tiempo de ejecución?

No es una forma estándar: no hay API para acceder a ellos.Los compiladores pueden tener algunas extensiones o API privadas para acceder a ellos, pero puede que sea solo una extensión.

¿Existe la vtable para todas las clases, o solo para aquellas que tienen al menos una función virtual?

Sólo aquellos que tienen al menos una función virtual (ya sea incluso destructora) o derivan al menos una clase que tiene su vtable ("es polimórfica").

¿Las clases abstractas simplemente tienen un NULL para el puntero de función de al menos una entrada?

Esa es una implementación posible, pero más bien no se practica.En su lugar, suele haber una función que imprime algo así como "función virtual pura llamada" y no abort().La llamada a eso puede ocurrir si intenta llamar al método abstracto en el constructor o destructor.

¿Tener una única función virtual ralentiza toda la clase?¿O solo la llamada a la función que es virtual?¿Y la velocidad se ve afectada si la función virtual se sobrescribe o no, o esto no tiene ningún efecto mientras sea virtual?

La ralentización sólo depende de si la llamada se resuelve como llamada directa o como llamada virtual.Y nada más importa.:)

Si llama a una función virtual a través de un puntero o referencia a un objeto, siempre se implementará como una llamada virtual, porque el compilador nunca puede saber qué tipo de objeto se asignará a este puntero en tiempo de ejecución y si es de un clase en la que este método se anula o no.Sólo en dos casos el compilador puede resolver la llamada a una función virtual como una llamada directa:

  • Si llama al método a través de un valor (una variable o resultado de una función que devuelve un valor), en este caso el compilador no tiene dudas sobre cuál es la clase real del objeto y puede "resolverlo" en el momento de la compilación. .
  • Si se declara el método virtual final en la clase a la que tienes un puntero o referencia a través de la cual lo llamas (sólo en C++11).En este caso, el compilador sabe que este método no puede sufrir ninguna anulación adicional y solo puede ser el método de esta clase.

Sin embargo, tenga en cuenta que las llamadas virtuales solo tienen la sobrecarga de desreferenciar dos punteros.Usar RTTI (aunque solo está disponible para clases polimórficas) es más lento que llamar a métodos virtuales, en caso de que encuentre un caso para implementar lo mismo de dos maneras similares.Por ejemplo, definir virtual bool HasHoof() { return false; } y luego anular sólo como bool Horse::HasHoof() { return true; } le proporcionaría la posibilidad de llamar if (anim->HasHoof()) eso será más rápido que intentarlo if(dynamic_cast<Horse*>(anim)).Esto es porque dynamic_cast tiene que recorrer la jerarquía de clases en algunos casos incluso de forma recursiva para ver si se puede construir la ruta a partir del tipo de puntero real y el tipo de clase deseado.Mientras que la llamada virtual es siempre la misma: desreferenciación de dos punteros.

Aquí hay un ejecutable Implementación manual de una tabla virtual en C++ moderno.Tiene una semántica bien definida, sin hacks ni void*.

Nota: .* y ->* son operadores diferentes a * y ->.Los punteros de función miembro funcionan de manera diferente.

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

Cada objeto tiene un puntero vtable que apunta a una matriz de funciones miembro.

Algo que no se menciona aquí en todas estas respuestas es que en el caso de herencia múltiple, donde todas las clases base tienen métodos virtuales.La clase heredera tiene múltiples punteros a un vmt.El resultado es que el tamaño de cada instancia de dicho objeto es mayor.Todo el mundo sabe que una clase con métodos virtuales tiene 4 bytes extra para el vmt, pero en caso de herencia múltiple es para cada clase base que tiene métodos virtuales multiplicado por 4.siendo 4 el tamaño del puntero.

Las respuestas de Burly son correctas aquí excepto por la pregunta:

¿Las clases abstractas simplemente tienen un NULL para el puntero de función de al menos una entrada?

La respuesta es que no se crea ninguna tabla virtual para las clases abstractas.¡No es necesario ya que no se pueden crear objetos de estas clases!

En otras palabras si tenemos:

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

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

El puntero vtbl al que se accede a través de pB será el vtbl de clase D.Así es exactamente como se implementa el polimorfismo.Es decir, cómo se accede a los métodos D a través de pB.No es necesario un vtbl para la clase B.

En respuesta al comentario de Mike a continuación...

Si la clase B en mi descripción tiene un método virtual foo() que no es anulado por D y un método virtual bar() eso se anula, entonces el vtbl de D tendrá un puntero a B foo() y a los suyos bar().Todavía no se ha creado ningún vtbl para B.

muy linda prueba de concepto que hice un poco antes (para ver si el orden de herencia importa);déjeme saber si su implementación de C++ realmente lo rechaza (mi versión de gcc solo da una advertencia para asignar estructuras anónimas, pero eso es un error), tengo curiosidad.

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

C Principal:

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

producción:

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

tenga en cuenta que como nunca asigno mi objeto falso, no hay necesidad de realizar ninguna destrucción;Los destructores se colocan automáticamente al final del alcance de los objetos asignados dinámicamente para recuperar la memoria del literal del objeto y del puntero vtable.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top