Как выровнять указатели при работе с множественным наследованием? - PullRequest
0 голосов
/ 15 апреля 2011

Скажем, у нас есть конкретный класс A и абстрактный класс B.

Рассмотрим конкретный C, который наследует от A и B и реализует B:

class C : public A, public B  
{  
/* implementation of B and specific stuff that belongs to C */  
};

ТеперьЯ определяю функцию с сигнатурой void foo(B* b);

Это мой код, я могу предположить, что все указатели на B являются и A, и B. В определении foo, как получить указатель на A?Противный, но работающий трюк состоит в том, чтобы выровнять указатели назад следующим образом:

void foo(B* b)  
{  
    A* a = reinterpret_cast<A*>(reinterpret_cast<char*>(b) - sizeof(A));
    // now I can use all the stuff from A  
}

Имейте в виду, что C не имеет супертипа, и на самом деле, есть много классов, похожих на C, которые являются только A и B.Не стесняйтесь подвергать сомнению и мою логику, и этот образец дизайна, но вопрос касается только выравнивания указателей.

Ответы [ 4 ]

6 голосов
/ 15 апреля 2011
void foo(B* b)  
{  
    //A* a = reinterpret_cast<A*>(reinterpret_cast<char*>(b) - sizeof(A)); // undefined behaviour!!!!
    A* a = dynamic_cast<A*>(b);
    if (a)
    {
       // now I can use all the stuff from A  
    }
    else
    {
       // that was something else, not descended from A
    }
}

Забыл сказать: для того, чтобы сделать работу динамической, как А, так и В должны иметь виртуальную функцию (и) или, по крайней мере, виртуальные деструкторы. В противном случае нет никакого законного способа сделать это преобразование типов.

2 голосов
/ 15 апреля 2011

Наличие огромного набора несвязанных классов, которые оба происходят от A и B, очень странный дизайн.Если есть что-то, что делает A и B всегда «используемыми вместе», вы можете либо объединить их, либо ввести класс подкладок , который только происходит от них, а затем только отэтот класс:

class Shim : A, B {};

class DerivedX : Shim {};

, и в последнем случае вы просто используете static_cast для первого понижения с A или B до Shim*, а затем в C ++ он неявно преобразует указатель Shim*в другой класс.

0 голосов
/ 15 апреля 2011

Расширяя ответ Sharp (и вводя его как ответ, потому что я не могу получить отформатированный код в комментарии), вы все равно можете использовать шим:

class Shim : public virtual A, public virtual B {};

Тогда:

class Derived1 : public Shim, public virtual A, public virtual B1
{
};

class Derived2 : public Shim, public virtual A, public virtual B2
{
};

B1 и B2 должны происходить практически из B.

Но я подозреваю, что если вам всегда нужно реализовать как A, так и B, вы должны создать единый интерфейс с обоими, либо наследование или объединение обоих в один класс; ваш B1 и B2 унаследовал бы от этого. (Решение с dynamic_cast, конечно, для случая, когда производная класс B может или не может также быть производным от A.)

0 голосов
/ 15 апреля 2011

Если вы хотите использовать функциональность как класса A, так и класса B в своей функции, то вам следует изменить функцию для получения указателей C:

void foo(C* c);

И в целом вы ошибаетесь, полагая, что«каждый B тоже есть A».Вы можете создавать классы, производные от вашего интерфейса B, а не от класса A, поэтому компилятор не будет знать, что в вашем конкретном случае «каждый B - это A».

...