в решении проблемы с бриллиантами, почему нам нужно виртуально наследовать класс прародителя два раза? - PullRequest
0 голосов
/ 29 октября 2019

В приведенном ниже коде, почему я должен наследовать класс A практически в обоих классах B и C?

Я узнал, что первый компилятор находит целевую функцию в производном классе. Если он не найден в нем, то компилятор должен искать в его базовых классах. Итак, если я фактически наследую только один раз (класс A), то компилятор должен найти целевую функцию (aa()) только по одному пути, то есть от D до C до A в следующем случае.

Если я унаследую его только один раз, проблема все равно останется.

#include <iostream.h>
using namespace std;


class A
{
public: 
    void aa() { cout<<"aa"<<endl; } 
};

class B: public virtual A
{  };

class C: public /*virtual*/ A
{  };

class D: public C,public B
{  };

int main()
{
    D d1;
    d1.aa();
    return 0;
}

Я получу эту ошибку:

Error : Member is ambiguous: 'A::aa' and 'A::aa' in function `main()`

Ответы [ 4 ]

0 голосов
/ 29 октября 2019

Если вы удалите ключевое слово virtual для класса C, у вас будет второй экземпляр A в дереве классов. И если у вас есть два раза в вашем классе, доступ к члену равен ambiguous!

Если вы расширите свой пример таким образом, вы можете увидеть оба экземпляра и избавиться от сообщения об ошибке:

int instance_count = 0;
class A
{
    int myInstance;

    public:

    A(): myInstance{ instance_count++ } { std::cout << "create instance # " << myInstance << std::endl;}

    void aa() { std::cout<<"Instance"<< myInstance << std::endl; }
};

class B:public A
{  };
class C:public /*virtual*/ A
{  };
class D:public C,public B
{  };

int main()
{
    D d1;
    d1.::C::aa(); // you can access each instance by giving the path through the hirarchy
    d1.::B::aa();
    return 0;
}
0 голосов
/ 29 октября 2019

Вот как это работает. From cppreference :

Виртуальные базовые классы

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

Возможно, вы понимаете, что«один раз унаследованный виртуальный базовый класс появляется только один раз как подобъект», однако более правильным неформальным способом было бы «все виртуальные унаследованные подобъекты выглядят только как один подобъект».

Рассмотрим надуманную ситуацию, когда baseAхочет, чтобы base появился в конечном производном классе. Он будет использовать не виртуальное наследование:

struct baseA : base {};

Теперь класс baseB хочет, чтобы его производный объект содержал субобъект base, который является общим для всех других виртуальных унаследованных базовых классов, он будет использовать виртуальное наследование:

struct baseB : virtual base {};

Теперь мы можем наследовать от обоих:

struct foo : baseA, baseB {};

Если виртуальное наследование будет работать так, как вы ожидали, это сломает baseA (предполагается, что foo получаетотдельный base подобъект). Также было бы нелогичным, если добавление наследования baseB превратит наследование baseA в виртуальное.

Это на самом деле всего лишь спекуляция, а TL; DR на самом деле просто: вот как работает виртуальное наследование по определению.

0 голосов
/ 29 октября 2019

Есть хорошее объяснение во включенной ссылке.

https://isocpp.org/wiki/faq/multiple-inheritance

Вам нужно прокрутить чуть ниже половины пути к разделу о "страшном алмазе". Непосредственно в разделе ниже объясняется, как обращаться с ужасным бриллиантом - с помощью ключевого слова «виртуальный». общий предок, вы получите две копии базового объекта. В вашем примере вы получаете две копии A - одну на B и одну на C. И затем, когда вы используете ее от D, ваш код не знает, какую именно вы имеете в виду. Это, вероятно, не то, что вы действительно хотите. Вам нужен только один экземпляр A, и вы хотите, чтобы B и C поделились им.

0 голосов
/ 29 октября 2019

Один ответ будет: просто потому что. Это правило. У тебя тоже есть. Точка.

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

Но более глубокий ответ заключается в том, что для виртуального это будет иметь мало смыслазагрязнять не виртуальные вещи.

Ожидаете ли вы, что не виртуальная функция-член станет виртуальной, потому что какая-то другая функция с такой же сигнатурой является виртуальной в каком-то другом классе, и в итоге они становятся двумя базами? Это реальный вопрос, на воображаемом языке, который вы бы составили для своей интуиции:

struct A {
   void f(); // non virtual!
};

struct AA : A {
   void f(); // non virtual, hides, does not override
};

struct B {
   virtual void f();
};

struct BB : B {
   void f(); // virtual overrider 
};

struct A {
   void f(); // non virtual!
};

struct AABB : AA, BB {
   // is AA::f() now virtual? what about A::f()?
};

Если вы не ожидаете, что виртуальность в BB изменит семантику AA, тогда выиметь общий ответ: написание виртуального не изменяет виртуальность какой-либо ранее установленной виртуальности.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...