Pergunta

Todos nós sabemos o que funções virtuais estão em C ++, mas como eles são implementados em um nível profundo?

Pode o vtable ser modificada ou mesmo diretamente acessada em tempo de execução?

Será que a exist vtable para todas as classes, ou apenas aqueles que têm pelo menos uma função virtual?

Não classes abstratas simplesmente ter um NULL para o ponteiro de função de pelo menos uma entrada?

Será que ter uma única função virtual abrandar toda a classe? Ou apenas o chamada para a função que é virtual? E se a velocidade ficar afectada se a função virtual é realmente substituído ou não, ou que isso tem nenhum efeito desde que é virtual.

Foi útil?

Solução

Como são funções virtuais implementadas em um nível profundo?

A partir "funções virtuais em C ++" :

Sempre que um programa tem uma função virtual declarado, uma v - tabela é construída para a classe. A mesa-v consiste de endereços para as funções virtuais para classes que contêm uma ou mais funções virtuais. O objecto da classe que contém a função virtual contém um apontador virtual que aponta para o endereço de base do quadro virtual na memória. Sempre que há uma chamada de função virtual, v-table é usado para resolver para o endereço de função. Um objeto da classe que contém um ou mais virtuais funções contém um ponteiro virtual chamado o vptr no início do objeto na memória. Por isso, o tamanho do objecto, neste caso, aumenta com o tamanho do ponteiro. Este vptr contém o endereço base da tabela virtual na memória. Note-se que tabelas virtuais são específicos de classe, ou seja, há apenas uma tabela virtual para uma classe, independentemente do número de funções virtuais que ele contém. Esta tabela virtual, por sua vez contém os endereços base de uma ou mais funções virtuais da classe. No momento em que uma função virtual é chamado em um objeto, o vptr desse objeto fornece o endereço base da tabela virtual para essa classe na memória. Esta tabela é usada para resolver a chamada de função, já que contém os endereços de todas as funções virtuais dessa classe. Isto é como dinâmica ligação é resolvido durante uma chamada de função virtual.

Pode o vtable ser modificada ou mesmo diretamente acessada em tempo de execução?

Universalmente, acredito que a resposta é "não". Você poderia fazer um pouco de memória deturpação de encontrar o vtable mas você ainda não sabe o que as assinaturas olhares funcionam como chamá-lo. Qualquer coisa que você gostaria de alcançar com essa capacidade (que os suportes de linguagem) deve ser possível sem acesso ao vtable direta ou modificá-lo em tempo de execução. Também nota, a C ++ especificação linguagem não especificar que vtables são necessários -. No entanto, que é como a maioria dos compiladores implementar funções virtuais

Será que a exist vtable para todos os objetos, ou apenas aqueles que têm pelo menos uma função virtual?

I acreditam a resposta aqui é "ele depende da implementação", já que a especificação não exige vtables em primeiro lugar. No entanto, na prática, eu acredito que todos os compiladores modernos apenas criam uma vtable se uma classe tem pelo menos 1 função virtual. Há um espaço sobrecarga associada com o vtable e um tempo de sobrecarga associada com a chamada uma função virtual vs uma função não-virtual.

Faça classes abstratas simplesmente ter um NULL para o ponteiro de função de pelo menos uma entrada?

A resposta é que não é especificado pela especificação linguagem por isso depende da implementação. Chamar a função puros resultados virtuais em um comportamento indefinido se não for definido (o que geralmente não é) (ISO / IEC 14882: 2003 10,4-2). Na prática, faz alocar um slot no vtable para a função, mas não atribuir um endereço para ele. Isto deixa o incompletos TabelaV que requer a classes derivadas para implementar a função e completar o TabelaV. Algumas implementações não basta colocar um ponteiro NULL na entrada vtable; outras implementações colocar um ponteiro para um método fictício que faz algo semelhante a uma afirmação.

Note que uma classe abstrata pode definir uma implementação para uma função virtual pura, mas que a função só pode ser chamada com uma sintaxe qualificado-id (ie., Especificando totalmente a classe no nome do método, semelhante ao chamar uma classe base método de uma classe de derivados). Isto é feito para fornecer um fácil de usar a implementação padrão, enquanto ainda exigindo que uma classe derivada forneça uma substituição.

Does ter uma única função virtual abrandar toda a classe, ou apenas a chamada para a função que é virtual?

Isso está ficando até a borda do meu conhecimento, então alguém por favor, ajude-me aqui se eu estiver errado!

I acreditam que apenas as funções que são virtual na experiência de classe o desempenho tempos hit relacionados a chamar uma função virtual vs. uma função não-virtual. A sobrecarga de espaço para a classe é lá de qualquer maneira. Note-se que se houver uma vtable, há apenas 1 por class , não um por objeto .

Será que a velocidade ficar afectada se a função virtual é realmente substituído ou não, ou faz isso não têm nenhum efeito desde que é virtual?

Eu não acredito que o tempo de execução de uma função virtual que é substituído diminui em comparação a chamar função virtual da base. No entanto, há uma sobrecarga de espaço adicional para a classe associada com a definição de outro TabelaV para a classe derivada contra a classe de base.

Recursos adicionais:

http : //www.codersource.net/published/view/325/virtual_functions_in.aspx (via máquina de volta way)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/ abi.html # vtable

Outras dicas

  • Pode o vtable ser modificada ou mesmo diretamente acessada em tempo de execução?

Não portably, mas se você não se importa truques sujos, com certeza!

AVISO : Esta técnica não é recomendada para uso por crianças, adultos com idade inferior a 969 , ou pequenas criaturas peludas de Alpha Centauri. Os efeitos colaterais podem incluir demônios que voam fora de seu nariz , o surgimento abrupto de Yog-Sothoth como exigido pela aprovação em todas as revisões de código subsequentes, ou a adição retroativa do IHuman::PlayPiano() para todos os existentes casos]

Na maioria dos compiladores que eu vi, o vtbl * é os primeiros 4 bytes do objeto, e os conteúdos VTBL são simplesmente uma matriz de ponteiros de membros lá (geralmente na ordem em que foram declaradas, com a classe base do primeiro) . Há, naturalmente, outros layouts possíveis, mas isso é o que eu geralmente observada.

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;

Agora, para puxar algumas travessuras ...

Alterar classe em tempo de execução:

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

Substituir um método para todas as instâncias (monkeypatching uma classe)

Este é um pouco mais complicado, uma vez que o próprio vtbl é, provavelmente, em memória só de leitura.

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

O último é bastante provável que faça vírus damas eo link acordar e tomar conhecimento, devido às manipulações MProtect. Em um processo usando o NX mordeu ele pode muito bem falhar.

Does ter uma única função virtual abrandar toda a classe?

Ou somente a chamada para a função que é virtual? E se a velocidade ficar afectada se a função virtual é realmente substituído ou não, ou que isso tem nenhum efeito desde que é virtual.

Tendo funções virtuais retarda toda a classe na medida em que mais um item de dados tem de ser inicializado, copiado, ... quando se lida com um objeto de tal classe. Para uma classe com meia dúzia de membros mais ou menos, a diferença deve ser neglible. Para uma classe que contém apenas um único membro char, ou não membros em tudo, a diferença pode ser notável.

Além disso, é importante notar que nem todas as chamadas para uma função virtual é uma chamada de função virtual. Se você tem um objeto de um tipo conhecido, o compilador pode emitir código para uma chamada de função normal, e pode até mesmo em linha disse função se ele se sente como ele. É só quando você faz chamadas polimórficas, através de um ponteiro ou referência que pode apontar para um objeto da classe base ou em um objeto de alguma classe derivada, que é necessário o engano vtable e pagar por ele em termos de desempenho.

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
}

Os passos do hardware tem que tomar são essencialmente os mesmos, não importa se a função será substituído ou não. O endereço do vtable é lida a partir do objeto, o ponteiro de função recuperado do slot apropriado, e a função chamada pelo ponteiro. Em termos de desempenho real, as previsões do ramo pode ter algum impacto. Assim, por exemplo, se a maioria de seus objetos referem-se à mesma implementação de uma dada função virtual, em seguida, há alguma chance de que o previsor de ramos irá prever corretamente que função a ser chamada antes mesmo do ponteiro foi recuperado. Mas não importa qual a função é o comum:. Poderia ser a maioria dos objetos delegando ao caso base não-substituído, ou a maioria dos objetos pertencentes à mesma subclasse e, portanto, delegando ao mesmo caso substituído

como eles são implementados em um nível profundo?

Eu gosto da idéia de jheriko para demonstrar isso usando uma implementação simulada. Mas eu uso C para implementar algo parecido com o código acima, de modo que o nível baixo é mais facilmente visto.

classe pai 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 derivada 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
}

função f realizar chamada de função 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);
}

Assim você pode ver, uma vtable é apenas um bloco estático na memória, contendo principalmente ponteiros de função. Cada objecto de uma classe polimórfica irá apontar para o TabelaV correspondente ao seu tipo dinâmico. Isso também faz a ligação entre RTTI e funções virtuais mais claras: você pode verificar que tipo de uma classe é simplesmente olhando o que VTable que aponta para. A descrição acima é simplificado, em muitos aspectos, como por exemplo herança múltipla, mas o conceito geral é o som.

Se arg é do tipo Foo* e você tomar arg->vtable, mas na verdade é um objeto do tipo Bar, então você ainda obter o endereço correto do vtable. Isso porque o vtable é sempre o primeiro elemento no endereço do objeto, não importa se ele é chamado vtable ou base.vtable em uma expressão corretamente digitado.

Normalmente com uma VTable, uma matriz de ponteiros para funções.

Esta resposta foi incorporado no resposta Wiki da Comunidade

  • fazer classes abstratas simplesmente ter um NULL para o ponteiro de função de pelo menos uma entrada?

A resposta para isso é que é não especificado - chamando a função puros resultados virtuais em um comportamento indefinido se não for definido (o que geralmente não é) (ISO / IEC 14882: 2003 10,4-2). Algumas implementações não basta colocar um ponteiro NULL na entrada vtable; outras implementações colocar um ponteiro para um método fictício que faz algo semelhante a uma afirmação.

Note que uma classe abstrata pode definir uma implementação para uma função virtual pura, mas que a função só pode ser chamada com uma sintaxe qualificado-id (ie., Especificando totalmente a classe no nome do método, semelhante ao chamar uma classe base método de uma classe de derivados). Isto é feito para fornecer um fácil de usar a implementação padrão, enquanto ainda exigindo que uma classe derivada forneça uma substituição.

Você pode recriar a funcionalidade de funções virtuais em C ++ usando ponteiros de função como membros de uma classe e funções estáticas como as implementações, ou usando ponteiro para funções de membro e funções membro para as implementações. Há vantagens única notação entre os dois métodos ... na verdade chamadas de função virtual são apenas uma notação conveniente si. Na verdade herança é apenas uma notação conveniente ... tudo pode ser implementado sem utilizar os recursos de linguagem para a herança. :)

A seguir é o código porcaria não testado, provavelmente, de buggy, mas demonstra espero que a idéia.

por exemplo.

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

Vou tentar torná-lo simples:)

Todos nós sabemos o que funções virtuais estão em C ++, mas como eles são implementados em um nível profundo?

Este é um array com ponteiros para funções, que são implementações de uma função virtual particular. Um índice nesta matriz representa índice específico de uma função virtual definido para uma classe. Isso inclui funções virtuais puros.

Quando um polimórficos deriva de classe de outra classe polimórfica, podemos ter as seguintes situações:

  • A classe derivada não adiciona novas funções virtuais nem substitui qualquer. Neste caso, esta classe compartilha o vtable com a classe base.
  • A classe derivando adiciona e substitui métodos virtuais. Neste caso, ele recebe o seu próprio vtable, onde as funções virtuais adicionados têm índice começando passado o último derivado.
  • Vários aulas polimórficas na herança. Neste caso, temos um índice de deslocamento entre a segunda e bases próximas e o índice do mesmo na classe derivada

Pode o vtable ser modificada ou mesmo diretamente acessada em tempo de execução?

Não maneira padrão - não há nenhuma API para acessá-los. Compiladores pode ter algumas extensões ou APIs privadas para acessá-los, mas isso pode ser apenas uma extensão.

Será que a exist vtable para todas as classes, ou apenas aqueles que têm pelo menos uma função virtual?

Apenas aqueles que têm pelo menos uma função virtual (seja ele mesmo destructor) ou derivar pelo menos uma classe que tem sua vtable ( "é polimórfico").

Faça classes abstratas simplesmente ter um NULL para o ponteiro de função de pelo menos uma entrada?

Essa é uma possível implementação, mas não praticado. Em vez disso há geralmente uma função que imprime algo como "função virtual pura chamada" e faz abort(). A chamada para que possa ocorrer se você tentar chamar o método abstrato no construtor ou destruidor.

Does ter uma única função virtual desaceleração toda a classe? Ou apenas o chamada para a função que é virtual? E faz a velocidade ficar afectada se a função virtual é realmente substituído ou não, ou que isso tem nenhum efeito desde que é virtual.

A desaceleração só é dependente se a chamada é resolvido como chamada direta ou como uma chamada virtual. E nada mais importa. :)

Se você chamar uma função virtual através de um ponteiro ou referência a um objeto, então ele vai ser sempre implementadas como chamada virtual - porque o compilador nunca pode saber que tipo de objeto será atribuído a este ponteiro em tempo de execução, e se é de uma classe em que este método é substituído ou não. Apenas em dois casos o compilador pode resolver a chamada para uma função virtual como uma chamada direta:

  • Se você chamar o método através de um valor (uma variável ou resultado de uma função que retorna um valor) - neste caso o compilador não tem dúvidas que a classe real do objeto é, e pode "hard-determinação" que em tempo de compilação.
  • Se o método virtual é declarada final na classe para a qual você tem um ponteiro ou referência através do qual você chamá-lo ( apenas em C ++ 11 ). Neste caso compilador sabe que este método não pode sofrer qualquer outra primordial e que só pode ser o método dessa classe.

Note que embora as chamadas virtuais têm apenas sobrecarga de dereferencing dois ponteiros. Usando RTTI (embora só está disponível para classes polimórficas) é mais lento do que chamar métodos virtuais, você deve encontrar um caso para implementar a mesma coisa duas dessas maneiras. Por exemplo, definindo virtual bool HasHoof() { return false; } e, em seguida, substituir apenas como bool Horse::HasHoof() { return true; } iria fornecer-lhe com capacidade de if (anim->HasHoof()) chamada que será mais rápido do que tentar if(dynamic_cast<Horse*>(anim)). Isso ocorre porque dynamic_cast tem para percorrer a hierarquia de classes, em alguns casos, até mesmo de forma recursiva para ver se há pode ser construído o caminho do tipo de ponteiro real eo tipo de classe desejada. enquanto the chamada virtual é sempre o mesmo -. dereferencing dois ponteiros

Aqui é um executável aplicação manual dos tabela virtual em C ++ moderno. Tem semântica bem definida, sem cortes e sem void*.

Nota: .* e ->* são diferentes operadores que * e ->. ponteiros de função membro funcionam de forma 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 objecto tem um ponteiro TabelaV que aponta para um conjunto de funções de membro.

Algo não mencionado aqui em todas essas respostas é que em caso de herança múltipla, onde as classes base todos têm métodos virtuais. A classe herdando tem vários ponteiros para um VMT. O resultado é que o tamanho de cada instância de um objecto, tal é maior. Todo mundo sabe que uma classe com métodos virtuais tem 4 bytes extra para a VMT, mas em caso de herança múltipla é para cada classe base que tem métodos tempos virtuais 4. 4 é o tamanho do ponteiro.

As respostas de Burly estão corretas aqui, exceto para a pergunta:

do classes abstratas simplesmente ter um NULL para o ponteiro de função de pelo menos uma entrada?

A resposta é que nenhuma tabela virtual é criado em tudo para classes abstratas. Não há necessidade desde há objetos destas classes pode ser criado!

Em outras palavras, se temos:

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

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

O ponteiro vtbl acessado através pB será o vtbl de classe D. Isto é exatamente como polimorfismo é implementado. Ou seja, como métodos D são acessados ??através do OP. Não há necessidade de uma vtbl para a classe B.

Em resposta ao comentário de Mike abaixo ...

Se a classe B em minha descrição tem um método virtual foo () que não é substituído por D e um método virtual bar () que é substituído, em seguida, D's vtbl terá um ponteiro para B do foo () e ao seu próprio bar () . Ainda não há vtbl criado para B.

prova muito bonito de conceito que eu fiz um pouco mais cedo (para ver se a ordem das questões de herança); deixe-me saber se a sua implementação de C ++, na verdade, rejeita-lo (a minha versão do gcc só dá um aviso para atribuir estruturas anônimos, mas isso é um erro), eu sou curioso.

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

saída:

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

nota desde que eu nunca estou alocando meu objeto fake, não há necessidade de fazer qualquer destruição; destruidores são colocados automaticamente no final do escopo de objetos alocados dinamicamente para recuperar a memória do objeto literal em si eo ponteiro vtable.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top