Интерфейс vtable

У интерфейсов (полиморфный класс исключительно с чистыми виртуальными функциями) есть vtable? Так как интерфейсы не реализуют полиморфную функцию сами по себе и не могут быть сконструированы напрямую, нет никакой необходимости, чтобы компоновщик размещал таблицу vtable. Это так? Я особенно обеспокоен компилятором MSVC.

Ответ 1

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

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

Есть еще одна причина для размещения виртуальной таблицы в базовом классе, даже если все ее методы являются чистыми виртуальными, и нет других элементов данных. Когда полиморфизм используется, указатель на базовый класс передается по всей программе. Чтобы вызвать виртуальный метод, компилятор/среда выполнения должны определить относительное смещение виртуальной таблицы от базового указателя. Если у С++ не было множественного наследования, можно было бы принять нулевое смещение от абстрактного базового класса (например), и в этом случае было бы возможно не иметь там vtable (но мы все еще нуждаемся в нем по причине № 1). Но поскольку существует множественное наследование, трюк ala "vtable присутствует в 0 смещении" не будет работать, потому что может быть два или три vtables в зависимости от числа (и типа) базовых классов.

Могут быть и другие причины, которых я не знаю.

Надеюсь, что это поможет.

Ответ 2

С чисто С++ точки зрения это академический вопрос. Виртуальные функции не должны быть реализованы с помощью vtables, если они не являются переносимым способом доступа к ним.

Если вас особенно беспокоит компилятор MSVC, вы можете украсить свои интерфейсы __declspec(novtable).

(В общем случае в общих реализациях абстрактному классу может понадобиться vtable, например:

struct Base {
    Base();
    virtual void f() {}
    virtual void g() = 0;
};

void h(Base& b) {
    b.f(); // Call f on a Base that is not (yet) a Derived
           // vtable for Base required
}

Base::Base() {
    h(*this);
}

struct Derived : Base {
    void g() {}
};

int main() {
    Derived d;
}

)

Ответ 3

vtable не требуется, но редко оптимизируется. MSVC предоставляет расширение __declspec(novtable), которое явно указывает компилятору, что vtable можно удалить. В отсутствие этого компилятор должен проверить себя, что vtable не используется. Это не исключительно сложно, но все же далеко не тривиально. И поскольку он не обеспечивает реальные преимущества скорости в обычном коде, проверка не выполняется в любом компиляторе, который я знаю.