Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

От «Виртуальные функции в C++»:

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

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

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

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

я полагать ответ здесь: «Это зависит от реализации», поскольку спецификация вообще не требует виртуальных таблиц.Однако на практике я считаю, что все современные компиляторы создают виртуальную таблицу только в том случае, если класс имеет хотя бы одну виртуальную функцию.Существуют накладные расходы на пространство, связанные с виртуальной таблицей, и накладные расходы по времени, связанные с вызовом виртуальной функции по сравнению с невиртуальной функцией.

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

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

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

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

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

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

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

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

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

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

Другие советы

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

Не портативно, но если вы не против подвохов, конечно!

ПРЕДУПРЕЖДЕНИЕ:Этот метод не рекомендуется использовать детям, взрослым в возрасте до 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.В процессе, использующем бит NX, вполне может произойти сбой.

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

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

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

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

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

Мне нравится идея Джерико продемонстрировать это с помощью макетной реализации.Но я бы использовал 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!
}

производный класс Бар

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

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

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

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

Этот ответ был включен в Ответ сообщества Wiki

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вот работоспособный ручная реализация виртуальной таблицы в современном 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
}

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

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

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

Имеют ли абстрактные классы просто 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.Для класса B нет необходимости в vtbl.

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

Если у класса B в моем описании есть виртуальный метод фу() это не переопределяется D и виртуальным методом бар() это переопределено, то vtbl D будет иметь указатель на B фу() и самому себе бар().Для B до сих пор не создан 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top