Вопрос о виртуальном наследовании интерфейсов.
От: Rakafon Украина http://rakafon.blogspot.com/
Дата: 05.10.09 16:03
Оценка:
Добрый день коллеги!
Есть вопрос о виртуальном наследовании интерфейсов.

Вот код функции 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) Мейер Ансельм Ротшильд , банкир.
virtual inheritance
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.