2015-07-20 1 views
5

Проблема в том, что трудно описать, поэтому код размещен спереди для лучшей ясности.Множественное наследование Виртуальная вызов Ambiguity

struct Base 
{ 
    int b; 
    virtual void foo(){cout << b << endl;} 
    Base(int x) : b(x){} 
}; 

struct Derived1 : Base //not virtual 
{ 
    virtual void foo(){/*Derived2's code*/} 
    Derived1() : Base(1){} 
}; 

struct Derived2 : Base //not virtual 
{ 
    virtual void foo(){/*Derived2's code*/} 
    Derived2() : Base(2){} 
}; 

struct MultiInheritance : Derived1, Derived2 
{ 
    void bar1() 
    { 
     //needs to access Derived1's Base foo() 
    } 
    void bar2() 
    { 
     //needs to access Derived2's Base foo() 
    } 
}; 

Предположим, что в какой-то странный причудливый сценарий, я хотел бы базовый класс MultiInheritance, который имеет два базовых класса Derived1 и Derived2, которые имеют общую невиртуальном базового класса Base.

Есть два Base в MultiInheritance, как указать, который Base класс Я хотел бы получить доступ в MultiInheritance?

Вышеприведенный код, кажется, работает нормально, произнося несколько раз, но я не уверен, если это определено поведением или нет. Если да, то как это реализовано компилятором для удовлетворения потребностей полиморфизма? С одной стороны, вызовы virtual должны приводить к одной и той же таблице функций virtual, а с другой, если она не выводит разные ответы.

EDIT

Я хотел бы подчеркнуть, что Base классов должны быть невиртуальным

EDIT2

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

+0

Какой из них вы считаете * правильный * 'Base'? Или ваш вопрос, как сделать так, чтобы существовала только одна «База»? – Barry

+0

Ваш анализ верен, и компилятор построит правильную таблицу vtable, отражающую существование двух базовых классов. Чтобы этого избежать, вам нужно виртуальное наследование, и структура vtable станет более сложной. –

+1

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

ответ

0

Вот еще более наглядный пример.

#include <iostream> 
using namespace std; 

template <typename Res, typename Arg> 
Res& as(Arg& arg) 
{ 
    return arg; 
} 

struct Base 
{ 
    virtual void foo() = 0; 
}; 

struct Derived1 : Base {}; 
struct Derived2 : Base {}; 

struct MoreDerived1 : Derived1 
{ 
    void foo() { cout << "Derived1\n"; } 
}; 

struct MoreDerived2 : Derived2 
{ 
    void foo() { cout << "Derived2\n"; } 
}; 

struct MultiInheritance : MoreDerived1, MoreDerived2 
{ 
    void bar1() { as<Derived1>(*this).foo(); } 
    void bar2() { as<Derived2>(*this).foo(); } 
}; 

int main() 
{ 
    MultiInheritance m; 
    m.bar1(); 
    m.bar2(); 
} 

Этот пример показывает, что:

  1. Вам не нужно указать, какие базовые вам нужно явно используя полный путь наследования, достаточно спуститься к подобъекту, который имеет однозначную базу subobject
  2. Здесь работает механизм виртуальных функций. Это не сработает, если вы попытаетесь позвонить Derived1::foo().

as вспомогательная функция просто синтаксический сахар, можно также сказать,

Derived1& d = *this; 
d.foo(); 
2

Это известно как проблема с алмазом.

http://www.cprogramming.com/tutorial/virtual_inheritance.html

Если вы хотите сохранить базу невиртуальной и получить поведение, которую Вы ищете сейчас, вы можете сделать это внутри MultipleInheritance следующий образом, чтобы убедиться, что вы вызываете функцию foo() от правильного базового класса

struct MultiInheritance : Derived1, Derived2 
{ 
    void bar1() 
    { 
     Derived1::foo(); 
    } 
    void bar2() 
    { 
     Derived2::foo(); 
    } 
}; 
0

Есть две базы в MultiInheritance, как указать, какой базовый класс Я желаю, чтобы получить доступ в MultiInheritance?

У вас есть двусмысленность, в которой базовый объект, которому вы звоните в

void MultiInheritance::bar1(){ 
    foo(); 
} 

То, как разрешить это, указав компилятору, где искать обув.

void MultiInheritance::bar1(){ 
    Derived1::foo(); // The same foo() as in your question. 
} 

Это то, что достигается за счет вашего

void MultiInheritance::bar1() 
{ 
    Derived1& d = *this; 

Это описано в стандарте по §10.2.12. Это четко определено. Как и ваша цепочка

void MultiInheritance::bar1() 
{ 
    Derived1& d = *this; 
    Base& b = *this; 

по этому же абзацу.

К сожалению, оператор разрешения области видимости не может заставить вас перейти от MultiInhteritance непосредственно на базу, как

MultiInheritance::foo(){ 
    Derived1::Base::foo(); 

в описании вложенная класса Base.

Чтобы перейти к функции foo(), принадлежащей Base, вы используете синтаксис разрешения разрешения в MultiInheritance, а также в Derived1 и Derived2.

Derived1()::foo(){ 
    Base::foo; 

Если это неуместно, вариант, который вы предложили, является оставшейся опцией.

Если это так, то как это реализовано компилятором для удовлетворения потребностей полиморфизма? С одной стороны, виртуальные вызовы должны приводить к одной и той же таблице виртуальных функций , но с другой стороны, если это не так, то выдают разные ответы.

Реализации компилятора отличаются компилятором, и, как заявил комментатор, использование виртуальных функций vtables является деталью реализации. И если в реализации используется виртуальная функция vtable, для реализации необходимо учитывать этот сценарий.