2013-03-06 3 views
1

я получил следующий набор классов:множественного наследования литья из базового класса к другому производному классу

enter image description here

И следующий фрагмент кода:

A* a; 
if(condition) 
{ 
    a = new E(); 
} 
else 
{ 
    a = new D(); 
} 

Теперь, учитывая, что там является такой функцией, как F::foo(), для того, чтобы назвать это, я должен отличить a либо E*, либо D*:

if(condition) 
{ 
    ((E*)a)->foo(); 
} 
else 
{ 
    ((D*)a)->foo(); 
} 

Насколько я знаю, отбрасывая a к F*, чтобы вызвать F::foo было бы незаконным, поскольку a имеет тип A*; и мне, проверяя условие перед вызовом foo, звучит как проблема дизайна. Может кто-нибудь, пожалуйста, дайте несколько советов относительно того, как я могу улучшить эту иерархию классов?

p.s. Использовал this tool, чтобы нарисовать диаграмму.

+1

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

+0

Из интереса, почему вы назначили свой 'новый E()' на 'A *' в первую очередь? В ответах ниже рассматриваются несколько различных подходов. Некоторые из них меняют существующую иерархию классов, другие - нет. Может быть, ответ на этот вопрос о «A *» приводит к определенному решению. –

+0

@SteveJessop Я должен использовать общий базовый класс, который был либо 'A', либо' F' для создания экземпляров 'E' и' D'. Ответы, предполагающие, что 'F' могут быть получены от' A' или наоборот, не соответствуют моим требованиям к дизайну, поскольку функциональность 'F' и' A' не имеет значения. 'F' не является интерфейсом, и им нужно наследовать как' E', так и 'D'.Я думаю, что я мог бы также выбрать «F» в качестве базового базового класса, но тогда мне нужно было бы использовать 'a' для' A', 'C' или' B', когда это необходимо. Пока что ваш ответ кажется лучшим решением. – Meysam

ответ

5
#include <iostream> 

struct A { virtual ~A() {} }; 

struct C : virtual A {}; 

struct B : virtual A {}; 

struct F { 
    virtual void Foo() { std::cout << "ok\n"; } 
}; 

struct E : C, virtual F {}; 

struct D : B, virtual F {}; 


int main() { 
    A *a = new E(); 
    dynamic_cast<F*>(a)->Foo(); 
} 
  • Если вы запутались и referand из a не является экземпляр F, то dynamic_cast возвращает нулевой
  • Если вы не используете виртуальное наследование, то вы можете в конечном итоге неоднозначные базовые классы. A dynamic_cast к неоднозначной базе сбой (возврат null). В этом примере нет двусмысленных оснований, но вы должны знать об этом.
  • Я оставил виртуальные деструкторы на большинстве классов, но только потому, что я ленив.

Если вы постоянно оказываетесь дело с объектами, которые являются экземплярами как A и F то, что должно быть отражено в иерархии классов, если это возможно. Например, вы можете определить тип G, который наследует фактически от A и F. Тогда D и E могут наследовать от G вместо F, и вы можете передать G* этому коду, который ожидает A*, на котором он может позвонить Foo().

+0

Точно, я также просто попробовал это - на gcc, даже C-стиль, сделанный из вопроса работает - это будет UB? –

+1

@Andreas: 'static_cast' для' E * 'и' D * 'в порядке (но, конечно, вы должны иметь возможность оценить то же условие, что и с момента создания объекта, чтобы применить к правильному типу). Вы не можете 'static_cast' от' A * 'до' F * ', хотя, потому что это несвязанные классы. –

+0

Правильно - важно то, что вы можете 'dynamic_cast' на' F * '... –

2

Трудно дать вам советы по дизайну, не зная точных семантики ваших классов (буквы - это просто символы, поэтому нужно предполагать, что эти отношения наследования в порядке, в то время как они могут и не быть).

Просто посмотрев на официальную организацию вашей модели, я бы сказал, что вы можете добавить виртуальную функцию в A, которая и D, и E будет отменена. Затем эти переопределения делегируют реализацию до F::foo().

class A { 
public: 
    virtual void bar() { }; // Maybe make this pure if A is abstract 
    // ... 
}; 

// ... 

class D : public C, public F { 
public: 
    virtual void bar() { /* ... */ f::foo(); /* ... */ } 
    // ... 
}; 

class E : public B, public F { 
public: 
    virtual void bar() { /* ... */ f::foo(); /* ... */ } 
    // ... 
}; 
+1

Это загрязняет интерфейс 'A' с функциональностью' F'. Если это приемлемо, просто добавьте все виртуальные функции 'F' в' A' и забудьте о 'F'. –

1

Если F is только деталь реализации, тогда вы должны сделать то, что сказал @AndyProwl. Создайте виртуальную функцию в базовом классе A.

Если Fне просто деталь реализации, альтернатива, чтобы сохранить списки объектов, которые вы хотите иметь дело с как F с, и объекты, которые вы хотите иметь дело с как A с. Опять же, как говорит Энди, это будет зависеть от семантики вашей ситуации.

vector<F*> effs; 
vector<A*> ehs; 

A* a; 
F* f; 
if(condition) { 
    E* e = new E(); 
    a = e; 
    f = e; 
} 
else { 
    D* d = new D(); 
    a = d; 
    f = d; 
} 

effs.push_back(f); 
ehs.push_back(a); 

for(A* a: ehs) { 
    a->bar(); 
} 
for(F* f: effs) { 
    f->foo(); 
} 
+0

Если 'F' - это просто деталь реализации (например,' F' - это конкретный класс, который обеспечивает некоторую общую реализацию), это шаблон mixin, и ему не нужен (и не должен иметь) указатель на 'F'. –

+0

@JamesKanze Да, вот что я имел в виду. Если 'F' не является деталью реализации, альтернативой является ... я буду обновлять. –

2

Не зная роли различных классов, это трудно сказать, но если A и F интерфейсы (вероятно, случай), то дано A*, правильный способ спросить, есть ли объект также поддерживает интерфейс F is dynamic_cast<F*>. Это дает указатель на интерфейс F, если он поддерживается, и указатель на нуль в противном случае.

Кроме того, вы можете подумать, поддерживает ли интерфейс F интерфейс A, или полностью ли он не связан. Если это является расширением, то F следует, вероятно, получить от A; когда создает объект, известный для реализации расширенного интерфейса, вы назначаете его адрес F* и избегаете всех будущих трансляций. (В общем, не назначайте на A*, пока не достигнет точки , где некоторые из объектов указывает на хотение не реализовать F.) Таким образом, вы в конечном итоге с чем-то вроде:

// interfaces... 
class A {}; 
class F : public virtual A {}; 

// implementations of A... 
class C : public virtual A {}; 
class B : public virtual A {}; 

// implementations of F (and also A, of course) 
class E : public C, public virtual F {}; 
class D : public B, public virtual F {}; 

Примечание что при выходе из интерфейса обычно - хорошая идея сделать деривацию виртуальной. . (В этом случае требуется для всех дифференцированиях A Но так же шаблон может повторяться на другом уровне, с какой-то новый класс простирающейся интерфейс F, это вообще проще просто принять правило: вывод из интерфейс является виртуальным.)

Если F действительно не имеет отношения к A, тогда вы даже можете спросить, что один класс делает реализацию обоих. Или, если это имеет смысл, что некоторые (многие?) Реализации A также реализовать F, вы могли бы рассмотреть вопрос о предоставлении доступа к F как часть интерфейса из A: скажем, виртуальная функция F* A::getF() { return NULL; }; классы, которые также реализуют F, переопределяют эту функцию с чем-то вроде F* E::getF() { return this; }.

+0

Я согласен со всем этим. Наши два ответа начинаются в одном и том же месте, а затем предлагают несколько разные варианты из сферы возможностей в зависимости от реальных деталей этих классов. Важным является правило, что «вывод из интерфейсов является виртуальным». –

+0

@SteveJessop Да. Интересно отметить, что вы единственный, кто отвечает за виртуальное наследование. Может быть, мы единственные, кто использует C++ как язык OO (среди других парадигм). (Мы также, похоже, единственные, кто думал о интерфейсах 'A' и' F'.) –

+0

Да, это интересно об интерфейсах.Я не думаю, что все программисты на С ++, даже те, которые используют динамический полиморфизм, действительно различают интерфейсы и другие базы. Для меня тот факт, что 'A *' передается и что получающий код хочет вызвать функцию 'F', означает, что' A' и 'F' * являются * интерфейсами (или если' F' является mixin то все же 'Foo' должен быть в некотором интерфейсе, который мы должны добавить). Если у них также есть какая-то реализация, запутанная в них, ну, это может быть неудачно из организации POV кода, но это не мешает им быть интерфейсами :-) –

Смежные вопросы