it-swarm.com.ru

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

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

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

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

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

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

99
Brian R. Bondy

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Дополнительные ресурсы:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (через обратную машину)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable

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

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

WARNING: этот метод не рекомендуется использовать детям, взрослым в возрасте до 969 или маленьким пушистым существам от Альфы Центавра. Побочные эффекты могут включать демоны, которые вылетают из вашего носа , внезапное появление Yog-Sothoth в качестве обязательного утверждающего во всех последующих проверках кода или ретроактивное добавление - 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. В процессе, использующем бит NX, он может потерпеть неудачу.

28
puetzk

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

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

Наличие виртуальных функций замедляет весь класс, поскольку еще один элемент данных должен быть инициализирован, скопирован ... при работе с объектом такого класса. Для класса с полдюжиной членов или около того, разница должна быть незначительной. Для класса, который содержит только один член 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 в правильно набранном выражении.

17
MvG

Вот 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
}
2
Xeverous

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

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

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

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

  • Производный класс не добавляет новые виртуальные функции и не переопределяет их. В этом случае этот класс делит виртуальную таблицу с базовым классом.
  • Производный класс добавляет и переопределяет виртуальные методы. В этом случае он получает свой собственный 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 в некоторых случаях должен проходить по иерархии классов, даже рекурсивно, чтобы выяснить, можно ли построить путь из фактического типа указателя и требуемого типа класса. При этом виртуальный вызов всегда один и тот же - разыменование двух указателей.

2
Ethouris

Этот ответ был включен в Community Wiki answer

  • У абстрактных классов просто NULL для указателя функции хотя бы на одну запись?

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

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

2
Michael Burr

Вы можете воссоздать функциональность виртуальных функций в 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; }
...
};
2
jheriko

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

2
Lou Franco

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

1
Philip Stuyck

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

1
who

очень милое доказательство концепции, которое я сделал немного раньше (чтобы увидеть, имеет ли значение порядок наследования); дайте мне знать, если ваша реализация 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.

0
Dmitry

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

У абстрактных классов просто 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's foo () и на собственный bar (). Там еще нет vtbl, созданный для B.

0
Andrew Stein