Почему указатель таблицы виртуальных функций (vfptr) не может быть статическим в C ++?

Если таблица виртуальных функций одинакова для всех объектов класса, то почему указатель на эту таблицу (vfptr) не может быть статическим и использоваться совместно для всех объектов?

5

Решение

Vtable по существу статичен. Но вам нужен член vptr внутри объекта, чтобы выполнять виртуальную диспетчеризацию и другие операции RTTI.

В реализации vptr этот код C ++:

class Base {
public:
virtual void f();
};

class Derived : public Base {
public:
virtual void f();
};

может действовать аналогично чему-то вроде этого:

class Base {
public:
Base::Base() : m_vptr(&Base_vtab) {}
Base::Base(const Base& b) : m_vptr(&Base_vtab) {}
void f() { (m_vptr->f_impl)(this); }
protected:
struct VTable {
void (*f_impl)(Base* self);
};
const VTable* m_vptr;
static const VTable Base_vtab;
private:
static void Base_f(Base* self);
};

const Base::VTable Base::Base_vtab = { &Base::Base_f };

class Derived : public Base {
public:
Derived::Derived() : Base() { m_vtpr = &Derived_vtab; }
Derived::Derived(const Derived& d) : Base(d) { m_vptr = &Derived_vtab; }
Derived::~Derived() { m_vptr = &Base::Base_vtab; }
protected:
static const VTable Derived_vtab;
private:
static void Derived_f(Derived* self);
static void Derived_f(Base* self) { Derived_f(static_cast<Derived*>(self)); }
};

const Base::VTable Derived::Derived_vtab = { &Derived::Derived_f };
3

Другие решения

Таблица виртуальных функций [в предположении, что компилятор C ++ реализует динамическую диспетчеризацию] распределяется между всеми объектами класса. Однако каждый объект должен знать, какая таблица виртуальных функций релевантна для этого объекта. Это то, на что указывает «указатель таблицы виртуальных функций».

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

#include <iostream>
struct B {
virtual ~B() {}
virtual void f() = 0;
};
struct D1: public B {
void f() override { std::cout << "D1::f()\n"; }
};
struct D2: public B {
void f() override { std::cout << "D2::f()\n"; }
};

Таблица виртуальных функций для D1 а также D2 будет содержать подходящий указатель на D1::f() а также D2::f() соответственно. Когда компилятор видит вызов через указатель или ссылку на B в f() во время выполнения необходимо решить, какую функцию вызывать:

void g(B* base) {
base->f();
}

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

2

«Виртуальный» означает «определенный во время выполнения». «Статический» означает «определенный во время перевода».

Чтобы принимать решения во время выполнения, у вас должен быть параметр (например, vptr), значение которого можно установить динамически во время выполнения. То есть для данной ссылки на базовый объект xнам нужно какое-то значениеx.vptr«который содержит динамическую информацию (а именно информацию о наиболее производном классе которого x является базовым подобъектом).

1

class A
{
public:
virtual void Test();
...
};

class B: public A
{
public:
virtual void Test();
...
}

если vfptr является статическим для всех объектов, при компиляции кода ниже:

void DoTest(A* pA)
{
...
}

A* pA = new B;
DoTest(pA);

A :: vfptr будет распознаваться и использоваться компилятором, но это неожиданно!

0
По вопросам рекламы [email protected]