Неоднозначное наследование функции при множественном наследовании классов, которые сами имеют алмазное наследование в своей иерархии.

Описание слова (код ниже): у меня есть библиотека, содержащая набор классов. Для каждой группы классов у нас есть два конкретных типа (ClassA_Partial, ClassA), (ClassB_Partial, ClassB) и т. д. Каждый из этих реализует (Interface_Partial, Interface) соответственно. Кроме того, Interface является Interface_Partial, а каждый Class? является Class?_Partial, создавая ромбовидный шаблон наследования, в котором вершина наследуется виртуально.

Почему функции Interface_Partial неоднозначны при наследовании как ClassA, так и ClassB?

struct Interface_Partial
{ 
    virtual ~Interface_Partial(); 
    virtual void f() = 0;
};

struct Interface
    :
    virtual Interface_Partial
{
    virtual void g() = 0;
};


struct ClassA_Partial : public virtual Interface_Partial
{
    void f() {};
};


struct ClassA : public Interface, public virtual ClassA_Partial
{
    void g() {};
};

struct ClassB_Partial : public virtual Interface_Partial
{
    void f() {};
};


struct ClassB : public Interface, public virtual ClassB_Partial
{
    void g() {};
};

struct MyClass : public ClassA, public ClassB
{ }; // error C2250: MyClass : ambiguous inheritance of 'void Interface_Partial::f(void)'

Почему мы не можем устранить неоднозначность, как обычно, когда наследуем общий интерфейс более одного раза? Например

struct ClassX : public Interface_Partial { void f() {} };
struct ClassY : public Interface_Partial { void f() {} };
class Another : public ClassX, public ClassY
{};

void func()
{
    // This is ok
    Another a;
    a.ClassX::f();   

    // Why would this not work?
    // unambiguously refers to the one and only f() function 
    // inherited via  ClassA
    MyClass b;
    b.ClassA::f();   
}

person Zero    schedule 17.02.2015    source источник
comment
К счастью, «библиотека» в приведенном выше примере — это мой собственный код, и я уже избавляюсь от алмазного наследования только для того, чтобы сохранить рассудок. Однако меня интересуют технические причины, по которым это не работает.   -  person Zero    schedule 17.02.2015


Ответы (2)


Чтобы ответить на оба вопроса, нам нужно понять, как работает виртуальная функция

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

введите здесь описание изображения

Для Interface_Partial: Vtable будет содержать адрес функции f() с собственным определением, т. е. Interface_Partial :: f().

В классе/структуре Interface: у него есть функция g() со своим собственным определением, а также унаследованная функция f() от Interface_Partial, которую он не переопределил. Таким образом, Vtable класса Interface будет иметь адрес двух функций:

  1>    g() as  Interface :: g()

  2>    f() as  Interface_Partial :: f()

Теперь перейдем к ClassA_Partial: он переопределил функцию f() из своего родительского класса и дал собственное определение, поэтому виртуальная таблица ClassA_Partial будет иметь функции f() как:

ClassA_Partial :: f()

Теперь основная часть:

Если вы видите ClassA, он унаследован от Interface и ClassA_Partial и переопределяет g(), но не f(). Поэтому, когда компилятор увидит это, он будет сбит с толку, потому что теперь у него будет два определения f()

 1> as   Interface_Partial :: f()   
 2> as   ClassA_Partial :: f()

Какой выбрать? Interface_Partial или ClassA_Partial ! Поэтому он выдает ошибку, даже если вы делаете как

 b.ClassA::f();

Из-за двух разных версий f()

person A J    schedule 18.02.2015
comment
Я все еще не совсем понимаю ваше объяснение. Если я создам экземпляр ClassA, то ClassA::f() будет однозначным из-за виртуального наследования, поэтому я все еще думал, что b.ClassA::f() однозначен. - person Zero; 19.02.2015
comment
Поскольку ClassA имеет два определения функции f(), одно из Interface_Partial, а другое из ClassA_Partial. Поскольку вы наследуете интерфейс и ClassA_Partial, используя виртуал, поэтому у них будет одна копия функции f(), НО classA_Partial переопределяет f() и создает свою собственную версию f(), теперь, когда вы наследуете эти два интерфейса и ClassA_Partial, в классе Вы получите две разные версии f(). - person A J; 19.02.2015

person    schedule
comment
Я не понимаю, чем это отличается от примера класса Another, где мы наследуем ClassX и ClassY без двусмысленности, несмотря на наличие двух реализаций производного класса на одном уровне. Не могли бы вы немного пояснить это? - person Zero; 18.02.2015
comment
Значит ли это, что виртуальное наследование передается дочерним классам? Даже если я хочу знать только о ClassA и ClassB, мне все равно нужно знать, что каждый из них виртуально наследует f(), а это означает, что если я не виртуальный наследую ClassA и ClassB, компилятор все еще пытается виртуально наследовать оба f() и разрешать одну функцию, хотя на самом деле мне нужны и ClassA::f(), и ClassB::f() ? - person Zero; 19.02.2015
comment
@Zero, из-за виртуального наследования для базового класса Interface_Partial существует только одна виртуальная таблица - как только вы используете виртуальное наследование, виртуальность заражает все производные классы на всех уровнях. - person Lubo Antonov; 20.02.2015
comment
Я добавил ваш предыдущий комментарий к ответу, потому что для меня это была единственная информация, которую я действительно не понял. Спасибо. - person Zero; 23.02.2015