Добрый день коллеги!
Есть вопрос о виртуальном наследовании интерфейсов.
Вот код функции main, дальше опишу классы:
int _tmain(int argc, _TCHAR* argv[])
{
IInterface1* obj = new ObjStruct;
obj->AddRef();
obj->SomeMethod1();
IInterface2* obj2 = dynamic_cast<IInterface2*>(obj);
obj2->AddRef();
obj2->SomeMethod2();
obj2->Release();
obj->Release();
std::cout << "sizeof(IBase) = " << sizeof(IBase) << " bytes. "<< std::endl;
std::cout << "sizeof(IInterface1) = " << sizeof(IInterface1) << " bytes. "<< std::endl;
std::cout << "sizeof(IInterface2) = " << sizeof(IInterface2) << " bytes. "<< std::endl;
std::cout << "sizeof(ObjStruct) = " << sizeof(ObjStruct) << " bytes. "<< std::endl;
return 0;
}
Допустим есть следующая иерархия классов:
struct IBase
{
virtual void AddRef() = 0;
virtual void Release() = 0;
};
struct IInterface1 : public IBase
{
virtual void SomeMethod1() = 0;
};
struct IInterface2 : public IBase
{
virtual void SomeMethod2() = 0;
};
struct ObjStruct
: public IInterface1
, public IInterface2
{
void AddRef() { /*std::cout << "AddRef();" << std::endl;*/ };
void Release() { /*std::cout << "Release();" << std::endl;*/ };
void SomeMethod1() { /*std::cout << "SomeMethod1();" << std::endl;*/ };
void SomeMethod2() { /*std::cout << "SomeMethod2();" << std::endl;*/ };
};
Во время выполнения функции main, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 4 bytes.
sizeof(IInterface2) = 4 bytes.
sizeof(ObjStruct) = 8 bytes.
Press any key to continue . . .
... ну вроде как всё понятно ... IInterface1 — таблица функций, IInterface2 — таблица функций, объект наследует два интерфейса, получает две таблицы, соответственно два указателя. В каждой таблице, походу, будет три записи. И насколько я понимаю и в одной и в другой таблицах указатели на методы ObjStruct::AddRef() и ObjStruct::Release() будут указывать на одни и те же места в памяти.
Вот ...
Ежели сделать как следует: то есть повторяющийся базовый класс сделать виртуальным, ну типа чтобы он не повторялся в последнем наследнике дважды, то:
( ... для наглядности начинка интерфейсов опущена ... )
struct IBase { };
struct IInterface1 : virtual public IBase { };
struct IInterface2 : virtual public IBase { };
struct ObjStruct
: public IInterface1
, public IInterface2
{ };
Во время выполнения функции main, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 12 bytes.
sizeof(IInterface2) = 12 bytes.
sizeof(ObjStruct) = 20 bytes.
Press any key to continue . . .
Ну вот и что это такое? Никак дупля не поймаю. Откуда у IInterface1 и IInterface2 по 12 байтов внутрях? По три таблицы что ли? Тогда три таблицы на что? А у ObjStruct откуда тогда 20 байт? Получается пять таблиц что ли (4*5=20)? Опять же: на что указывают эти пять указателей? Ну ладно, на таблицу IBase, на таблицу IInterface1, на таблицу IInterface2, а ещё два куда смотрят?
При этом если добавить виртуальность и во время наследования интерфейсов IInterface1 и IInterface2:
struct IBase { };
struct IInterface1 : virtual public IBase { };
struct IInterface2 : virtual public IBase { };
struct ObjStruct
: virtual public IInterface1
, virtual public IInterface2
{ };
Во время выполнения функции main, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 12 bytes.
sizeof(IInterface2) = 12 bytes.
sizeof(ObjStruct) = 24 bytes.
Press any key to continue . . .
То есть получается само по себе виртуальное наследование добавляет ещё один указатель в объект? И что это за зверюга такая, на которую указывает энтот указатель?
Представим, что интерфейсы IBase, IInterface1 и IInterface2 — есть некие библиотечные сущности, которые поменять нельзя. Имеет ли тогда вообще какой-то смысл следующее виртуальное наследование:
struct IBase { };
struct IInterface1 : public IBase { };
struct IInterface2 : public IBase { };
struct ObjStruct
: virtual public IInterface1
, virtual public IInterface2
{ };
Во время выполнения функции main, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 4 bytes.
sizeof(IInterface2) = 4 bytes.
sizeof(ObjStruct) = 12 bytes.
И опять блин, левые четыре байтика как плата за виртуальное наследование ... Рецепторы на известном месте мне подсказывают, что виртуальное наследование интерфейсов IInterface1 и IInterface2 структурой ObjStruct лишено всякого разумного смысла, потому что от ObjStruct никто не наследуется ... Я прав?
Ну а теперь внимание! Самый главный вопрос!
Нужно ли вообще в вышеописанной иерархии интерфейсов/классов использовать виртуальное наследование? Ведь всё так красиво при обычном наследовании! Не прячет ли в самом первом примере двойное
невиртуальное наследование структуры IBase каких-то подводных камней? Может виртуальное наследование имеет смысл только при наследовании реальных классов, содержащих некие данные, а в случаях когда одна и та же абсолютно абстрактная структура наследуется несколько раз виртуальное наследование и не нужно вовсе? Как мне корректно отнаследоваться в вышеописанном случае?
P.S.: ... за всю свою практику с виртуальным наследованием интерфейсов столкнулся впервые (ну или может не замечал ранее
), и книжки Страуструпа как назло под рукой нету
P.P.S.: Заранее спасибо всем отозвавшимся.
"Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.