У меня есть заголовок С++, объявляющий класс, состоящий только из чистых виртуальных методов. У меня есть две библиотеки DLL, использующие этот заголовок (с одной реализацией этого интерфейса), но не связанные во время компиляции. Одна DLL динамически загружает другую, передавая ей указатель реализованного интерфейса. Используют ли эти DLL одну и ту же структуру виртуальной таблицы?
Использует ли чистый абстрактный класс С++, объявленный в заголовке, используемом в разных проектах (без привязки времени компиляции), одну и ту же модель виртуальной таблицы?
Ответы (4)
Ты в безопасности.
Порядок, в котором методы появляются в vftable
, определяется структурой базового класса, и это все, о чем вам следует заботиться. Но это зависит от компилятора, поэтому используйте тот же компилятор для создания dll. Не полагайтесь на их обратную совместимость (или, по крайней мере, проверьте документацию).
Предположим, у вас есть следующий заголовок:
//header.h
class A
{
public:
virtual void foo() = 0;
virtual void goo() = 0;
};
И у вас есть B.dll
со следующим классом:
class B : public A
{
public:
virtual void foo() {}
virtual void goo() {}
}
Теперь в X.dll
вы получаете указатель на A
, который является объектом B
, созданным в B.dll
.
Звонок
void test( A* a )
{
a->foo();
}
вызовет B::foo()
.
Вы можете попробовать один изящный эксперимент: скомпилировать B.dll
с header.h
, а когда вы скомпилируете X.dll
, инвертировать порядок методов в header.h
:
//header.h
class A
{
public:
virtual void goo() = 0;
virtual void foo() = 0;
};
В этом случае, хотя вы никогда не должны этого делать, тот же самый вызов test()
в X.dll
, вероятно, вызовет метод B::goo()
. Это потому, что X.dll
предполагает наличие vftable
в заголовке. Однако это неопределенное поведение; Я просто написал этот пример, чтобы подчеркнуть.
Конечно, заголовка класса достаточно для создания полного класса (речь идет о макете в памяти, о том, как все позиционируется, а не о реальных данных внутри него), включая точную структуру виртуальной таблицы.
Подумайте об этом, каждый связывающий объект (ваши файлы .cpp) компилируется отдельно, с общими только файлами заголовков, но во время компиляции компилятор должен знать точную структуру виртуальной таблицы для правильной маршрутизации виртуальных вызовов.
Кстати, глупый вопрос, чтобы прочитать...
vtable
, вы также нарушаете ODR и получаете забавные вещи... но это не связан с конкретным поведением, о котором идет речь в вопросе.
- person David Rodríguez - dribeas; 08.02.2012
Если я правильно понял, у вас есть что-то вроде этого:
A.h
class A
{
public:
virtual void foo()=0;
}
B.cpp
#include <A.h>
class B : public A
{
public:
void foo()
{}
}
C.cpp
#include <A.h>
void bar(A * a)
{}
Итак, B.cpp
имеет класс, реализующий A
, а C.cpp
имеет некоторую функцию, принимающую указатель на экземпляр A
(который вы предоставляете с экземпляром B
). Это правильно?
Если да, то да, они совместно используют виртуальную таблицу. Виртуальная таблица создается путем компиляции класса B
, а C.cpp
вообще не имеет собственных виртуальных таблиц.
Все это зависит от компилятора, но в целом, когда класс чисто виртуальный и ни одна из функций-членов не определена в единице трансляции, компилятор сгенерирует vtable
как слабый символ. В вашем конкретном случае разные единицы перевода будут генерировать отдельные точно равные vtable
для базового типа, а компоновщик/загрузчик отбросит все символы, кроме одного, как и с любым другим слабым символом (подумайте о шаблонной не встроенной функции) .
Однако обратите внимание, что генерация vtable
не стандартизирована, а это означает, что если вы смешиваете код из двух разных компиляторов или даже версий, вы можете вызвать нарушение ODR.