Как реализуются виртуальные функции и vtable?

мы все знаем, что такое виртуальные функции на C++, но как они реализованы на глубоком уровне?

можно ли изменить vtable или даже получить прямой доступ во время выполнения?

существует ли vtable для всех классов или только для тех, которые имеют хотя бы одну виртуальную функцию?

абстрактные классы просто имеют значение NULL для указателя функции хотя бы одной записи?

наличие одной виртуальной функции замедляет весь класс? Или только вызов функции, которая является виртуальной? И влияет ли скорость, если виртуальная функция фактически перезаписана или нет, или это не имеет никакого эффекта, пока она виртуальна.

12 ответов


как виртуальные функции реализуются на глубоком уровне?

С "виртуальные функции в C++"

всякий раз, когда программа объявляет виртуальную функцию, для класса создается v - таблица. V-таблица состоит из адресов виртуальных функций для классов, содержащих одну или несколько виртуальных функций. Объект класса, содержащего виртуальные функции виртуального указатель, который указывает на базовый адрес виртуальной таблицы память. Всякий раз, когда есть вызов виртуальной функции, v-таблица используется для разрешения адреса функции. Объект класса, который содержит один или несколько виртуальных функций содержит виртуальный указатель на vptr в самом начале объекта в памяти. Следовательно, размер объекта в этом случае увеличивается на размер указателя. Этот vptr содержит базовый адрес виртуальной таблицы в памяти. Обратите внимание, что виртуальные таблицы относятся к классу, т. е. существует только одна виртуальная таблица для класса, независимо от количества виртуальных функций содержит. Эта виртуальная таблица в свою очередь содержит базовые адреса одной или нескольких виртуальных функций класса. Во время вызова виртуальной функции для объекта vptr этого объекта предоставляет базовый адрес виртуальной таблицы для этого класса в памяти. Эта таблица используется для разрешения вызова функции, поскольку она содержит адреса всех виртуальных функций этого класса. Вот как решается динамическая привязка во время вызова виртуальной функции.

можно ли изменить vtable или даже получить прямой доступ во время выполнения?

универсально, я считаю, что ответ "нет". Вы можете сделать некоторые искажения памяти, чтобы найти vtable, но вы все равно не знаете, как выглядит сигнатура функции. Все, что вы хотите достичь с помощью этой способности (которую поддерживает язык), должно быть возможно без доступа к vtable напрямую или изменения его во время выполнения. Также обратите внимание, что C++ языковая спецификация не укажите, что требуются vtables-однако именно так большинство компиляторов реализуют виртуальные функции.

существует ли vtable для всех объектов или только для тех, которые имеют хотя бы одну виртуальную функцию?

Я верить ответ здесь "это зависит от реализации", так как спецификация не требует vtables в первую очередь. Однако на практике я считаю, что все современные компиляторы создают только vtable, если класс имеет по крайней мере, 1 виртуальная функция. Существует пространство накладные расходы, связанные с vtable и время накладные расходы, связанные с вызовом виртуальной функции против не виртуальной функции.

абстрактные классы просто имеют значение NULL для указателя функции хотя бы одной записи?

ответ не указан спецификацией языка, поэтому он зависит от реализации. Вызов чистой виртуальной функции приводит к неопределенному поведению, если она не определена (что обычно не так) (ISO / IEC 14882: 2003 10.4-2). На практике он выделяет слот в vtable для функции, но не назначает ей адрес. Это оставляет vtable неполным, который требует, чтобы производные классы реализовали функцию и завершили vtable. Некоторые реализации просто помещают нулевой указатель в запись vtable; другие реализации помещают указатель на фиктивный метод, который делает что-то похожее на утверждение.

обратите внимание, что абстрактный класс может определить реализация для чистой виртуальной функции, но эта функция может быть вызвана только с синтаксисом qualified-id (т. е., полное указание класса в имени метода, аналогично вызову метода базового класса из производного класса). Это делается для обеспечения простой в использовании реализации по умолчанию, при этом по-прежнему требуется, чтобы производный класс предоставлял переопределение.

наличие одной виртуальной функции замедляет весь класс или только вызов функции, которая виртуальный?

это становится краем моих знаний, поэтому кто-то, пожалуйста, помогите мне здесь, Если я ошибаюсь!

Я верить что только функции, которые являются виртуальными в классе, испытывают время выполнения, связанное с вызовом виртуальной функции против не виртуальной функции. Пространство над головой для класса есть в любом случае. Обратите внимание, что если есть vtable, есть только 1 per класс, а не одного объект.

влияет ли скорость, если виртуальная функция фактически переопределена или нет, или это не имеет никакого эффекта, пока она виртуальна?

Я не считаю, что время выполнения виртуальной функции, которая переопределяется, уменьшается по сравнению с вызовом базовой виртуальной функции. Однако для класса, связанного с определением другой vtable для производного класса против базы, существует дополнительное пространство накладных расходов класс.

Дополнительные Материалы:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (через путь назад machine)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable


  • можно ли изменить vtable или даже получить прямой доступ во время выполнения?

не переносно, но если вы не против грязных трюков, конечно!

предупреждение: эта техника не рекомендуется для использования детьми, взрослыми в возрасте 969, или маленькие пушистые существа с Альфа-Центавра. Побочные эффекты могут включать демоны, которые вылетают из вашего носа, резкое появление Йог-Сотхотх как обязательный утверждающий на всех последующих обзорах кода или ретроактивное добавление IHuman::PlayPiano() для всех существующих экземпляров]

в большинстве компиляторов, которые я видел, vtbl * - это первые 4 байта объекта, а содержимое vtbl-это просто массив указателей-членов (как правило, в том порядке, в котором они были объявлены, с первым базовым классом). Конечно, есть и другие возможные макеты, но это то, что я обычно наблюдаемый.

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;

теперь, чтобы вытащить некоторые махинации...

изменение класса во время выполнения:

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

замена метода для всех экземпляров (monkeypatching класса)

это немного сложнее, так как сам vtbl, вероятно, находится в памяти только для чтения.

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

последнее, скорее всего, заставит вирусные шашки и ссылку проснуться и обратить внимание из-за манипуляций mprotect. В процессе использования функция вполне может неудача.


наличие одной виртуальной функции замедляет весь класс?

или только вызов функции, которая является виртуальным? И влияет ли скорость, если виртуальная функция фактически перезаписана или нет, или это не имеет никакого эффекта, пока она виртуальна.

наличие виртуальных функций замедляет весь класс, поскольку еще один элемент данных должен быть инициализирован, скопирован, ... при работе с объектом такого класса. Для класс с полудюжиной членов или около того, разница должна быть ничтожной. Для класса, который содержит только один char член, или нет членов вообще, разница может быть заметной.

кроме того, важно отметить, что не каждый вызов виртуальной функции вызов виртуальной функции. Если у вас есть объект известного типа, компилятор может выдать код для обычного вызова функции и даже встроить указанную функцию, если это необходимо. Только когда ты это делаешь. полиморфные вызовы через указатель или ссылку, которые могут указывать на объект базового класса или на объект некоторого производного класса, что вам нужно косвенное обращение vtable и платить за него с точки зрения производительности.

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
}

шаги, которые должно предпринять оборудование, по существу одинаковы, независимо от того, перезаписана ли функция или нет. Адрес vtable считывается из объекта, указатель функции извлекается из соответствующего слота, а функция вызывается с помощью указатель. С точки зрения фактической производительности прогнозы филиалов могут оказать определенное влияние. Так, например, если большинство ваших объектов ссылаются на одну и ту же реализацию данной виртуальной функции, существует некоторая вероятность того, что предсказатель ветвей правильно предсказает, какую функцию вызывать еще до того, как указатель будет извлечен. Но не имеет значения, какая функция является общей: это может быть большинство объектов, делегирующих непереписанный базовый случай, или большинство объектов, принадлежащих к одному и тому же подкласс и, следовательно, делегирование в тот же перезаписанный случай.

как они реализуются на глубинном уровне?

мне нравится идея jheriko продемонстрировать это, используя макет реализации. Но я бы использовал C для реализации чего-то похожего на код выше, так что низкий уровень легче увидеть.

родительский класс 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!
}

производный класс 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
}

функция f выполняя виртуальную функцию звоните

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

таким образом, вы можете видеть, что vtable-это просто статический блок в памяти, в основном содержащий указатели функций. Каждый объект полиморфного класса будет указывать на vtable, соответствующий его динамическому типу. Это также делает связь между RTTI и виртуальными функциями более ясной: вы можете проверить, какой тип класса просто глядя на то, на что указывает vtable. Вышеизложенное упрощено во многих отношениях, например, множественное наследование, но общая концепция звук.

если arg типа Foo* а ты возьми arg->vtable, но на самом деле является объектом типа Bar, то вы все равно получите правильный адрес vtable. Вот ведь vtable всегда является первым элементом в адресе объекта, независимо от того, называется ли он vtable или base.vtable в правильно набранном выражении.


обычно с VTable, массив указателей на функции.


этот ответ был включен в сообщество Wiki ответ

  • абстрактные классы просто имеют значение NULL для указателя функции хотя бы одной записи?

ответ на это заключается в том, что он не определен - вызов чистой виртуальной функции приводит к неопределенному поведению, если она не определена (что обычно не так) (ISO/IEC 14882:2003 10.4-2). Некоторые реализации просто помещают нулевой указатель в vtable entry; другие реализации помещают указатель на фиктивный метод, который делает что-то похожее на утверждение.

обратите внимание, что абстрактный класс может определить реализацию для чистой виртуальной функции, но эта функция может быть вызвана только с синтаксисом qualified-id (т. е., полное указание класса в имени метода, аналогично вызову метода базового класса из производного класса). Это делается для обеспечения простой в использовании реализации по умолчанию, при этом по-прежнему требуется, чтобы производный класс обеспечить переопределение.


вы можете воссоздать функциональность виртуальных функций в C++, используя указатели функций в качестве членов класса и статические функции в качестве реализаций или используя указатель на функции-члены и функции-члены для реализаций. Между этими двумя методами есть только нотационные преимущества... на самом деле виртуальная функция вызывает лишь условной удобство сами. Фактически наследство является только условной удобство... все это может быть реализовано без использования языка возможности для наследования. :)

ниже дерьмо непроверенный, вероятно, багги код, но, надеюсь, демонстрирует идею.

например

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

я постараюсь сделать это просто:)

мы все знаем, какие виртуальные функции есть в C++, но как они реализованы на глубоком уровне?

это массив указателей на функции, которые являются реализациями виртуальной функции. Индекс в этом массиве представляет определенный индекс виртуальной функции, определенной для класса. Это включает чисто виртуальные функции.

когда полиморфный класс происходит от другого полиморфного класса, мы можем иметь следующие ситуации:

  • производящий класс не добавляет новые виртуальные функции и не переопределяет их. В этом случае этот класс разделяет vtable с базовым классом.
  • производный класс добавляет и переопределяет виртуальные методы. В этом случае он получает свою собственную vtable, где добавленные виртуальные функции имеют индекс, начинающийся с последнего производного.
  • несколько полиморфных классов в наследство. В этом случае у нас есть индекс-сдвиг между вторым а рядом базы и индекс его в производном классе

можно ли изменить vtable или даже получить прямой доступ во время выполнения?

не стандартный способ-нет API для доступа к ним. Компиляторы могут иметь некоторые расширения или частные API для доступа к ним, но это может быть только расширение.

существует ли vtable для всех классов или только для тех, которые имеют хотя бы одну виртуальную функцию?

только те, которые имеют хотя бы одну виртуальную функцию (будь то даже деструктор) или производный хотя бы один класс, который имеет свою vtable ("является полиморфным").

абстрактные классы просто имеют значение NULL для указателя функции хотя бы одной записи?

это возможная реализация, но скорее не практикуется. Вместо этого обычно есть функция, которая печатает что-то вроде "чистой виртуальной функции" и делает abort(). Вызов этого может произойти, если вы попытаетесь вызвать абстрактный метод в конструкторе или деструктор.

наличие одной виртуальной функции замедляет весь класс? Или только вызов функции, которая является виртуальным? И влияет ли скорость, если виртуальная функция фактически перезаписана или нет, или это не имеет никакого эффекта, пока она виртуальна.

замедление зависит только от того, разрешен ли вызов как прямой вызов или как виртуальный вызов. И ничто другое не имеет значения. :)

если вы вызываете виртуальную функцию через указатель или ссылка на объект, тогда он всегда будет реализован как виртуальный вызов - потому что компилятор никогда не может знать, какой объект будет назначен этому указателю во время выполнения и принадлежит ли он классу, в котором этот метод переопределен или нет. Только в двух случаях компилятор может разрешить вызов виртуальной функции как прямой призыв:

  • если вы вызываете метод через значение (переменной или результат функции, которая возвращает значение) - в этом случае компилятор нет сомнения, что фактический класс объекта, и может "трудно решить" его во время компиляции.
  • если виртуальный метод объявляется final в классе, к которому у вас есть указатель или ссылка, через которую вы называете его (только в C++11). В этом случае компилятор знает, что этот метод не может подвергаться дальнейшему переопределению и может быть только методом из этого класса.

обратите внимание, что виртуальные вызовы имеют только накладные расходы разыменования два указателя. Использование RTTI (хотя доступно только для полиморфных классов) медленнее, чем вызов виртуальных методов, если вы найдете случай реализовать то же самое двумя такими способами. Например, определение virtual bool HasHoof() { return false; } и затем переопределить только как bool Horse::HasHoof() { return true; } предоставит вам возможность позвонить if (anim->HasHoof()) это будет быстрее, чем пытаться if(dynamic_cast<Horse*>(anim)). Это потому что dynamic_cast должен пройти через иерархию классов в некоторых случаях даже рекурсивно, чтобы увидеть, можно ли построить путь из фактического указателя тип и желаемый тип класса. Хотя виртуальный вызов всегда один и тот же-разыменование двух указателей.


каждый объект имеет указатель vtable, который указывает на массив функций-членов.


Что-то не упомянуто здесь во всех этих ответах, что в случае множественного наследования, где все базовые классы имеют виртуальные методы. Наследующий класс имеет несколько указателей на vmt. В результате размер каждого экземпляра такого объекта больше. Всем известно, что класс с виртуальными методами имеет дополнительные 4 байта для vmt, но в случае множественного наследования это для каждого базового класса, который имеет виртуальные методы, умноженные на 4. 4 - размер указателя.


здесь runnable ручная реализация виртуальной таблицы в современном C++. Он имеет четко определенную семантику, никаких хаков и нет void*.

Примечание: .* и ->* являются различными операторами, чем * и ->. Указатели функций-членов работают по-разному.

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

ответы Берли здесь верны, за исключением вопроса:

абстрактные классы просто имеют значение NULL для указателя функции хотя бы одной записи?

ответ заключается в том, что для абстрактных классов вообще не создается виртуальная таблица. Нет необходимости, так как объекты этих классов не могут быть созданы!

другими словами, если у нас есть:

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

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

указатель vtbl, доступ к которому осуществляется через pB, будет vtbl класса D. Это именно так реализуется полиморфизм. То есть, как D методы доступны через pB. Нет необходимости в vtbl для класса B.

в ответ на комментарий Майка ниже...

Если класс B в моем описании имеет виртуальный метод foo () это не переопределяется D и виртуальным методом bar () это переопределено, тогда vtbl D будет иметь указатель на B foo () и bar (). Еще нет vtbl по создан для Б.


очень милое доказательство концепции, которую я сделал немного раньше(чтобы увидеть, имеет ли значение порядок наследования); дайте мне знать, если ваша реализация c++ фактически отвергает его (моя версия gcc дает только предупреждение для назначения анонимных структур, но это ошибка), мне любопытно.

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

выход:

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

Примечание. поскольку я никогда не выделяю свой поддельный объект, нет необходимости делать какие-либо разрушения; деструкторы автоматически помещаются в конец области динамически выделенных объектов, чтобы вернуть память самого литерала объекта и указателя vtable.