Неоднозначный обходной путь для множественного наследования?

#c #multiple-inheritance

#c #множественное наследование

Вопрос:

У меня есть базовый класс с именем animal, а также dog и cat, которые наследуются от Animal. И класс множественного наследования, называемый dogcat, который наследуется от dog и cat, в Animal у меня есть метод, называемый sleep . Когда я хочу использовать этот метод из dogcat, я получаю сообщение об ошибке «DogCat::sleep» неоднозначно, я понимаю проблему, но я прочитал в книге, что это должно быть возможно, когда вы объявляете sleep виртуальным — но это не работает.

Это невозможно, книга неверна или есть какой-либо обходной путь?

 class Animal
{
public:
    Animal(){}

    virtual void sleep()
    {
        cout << "zzzzzzzzz" << endl;
    }
    virtual void eat() = 0;

};

class Dog: public Animal
{
protected:
    Dog(){}

    virtual void eat() override
    {
        cout << "eats dogfood" << endl;
    } 
};

class Cat :public Animal
{
public:
    Cat(){}
    virtual void eat() override
    {
        cout << "eats catfood" << endl;
    }
};

class DogCat : public Dog, public Cat
{
public:
    DogCat(){}
    using Dog::eat;

};

int main(int argc, char** argv) {
    DogCat *DC = new DogCat();
    DC->sleep();//Error
}
  

Комментарии:

1. Сколько видов животных, о которых вы знаете, являются кошками и собаками?

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

Ответ №1:

У вас проблема с бриллиантами

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

«Алмазная проблема» (иногда называемая «смертоносным алмазом смерти»[4]) — это двусмысленность, возникающая, когда два класса B и C наследуют от A, а класс D наследует как от B, так и от C. Если в A есть метод, который переопределили B и C, а D не переопределяет его, то какую версию метода наследует D: B или C?

Итак. Теперь у вас есть два экземпляра A. Какое это решение? У вас есть два:

  1. Определите операцию ожидания в одном из подклассов и вызовите ее:
 class Cat :public Animal
{
    public:
        Cat(){}
        virtual void eat() override
        {
            cout << "eats catfood" << endl;
        }

        void sleep()
        {
            Animal::sleep();
        }
};

int main(int argc, char** argv) {
    DogCat *DC = new DogCat();
    DC->Cat::sleep();
}
  
  1. Используйте виртуальное наследование, как сказано в ответе @Asesh. Проблема заключается в распространенном методе eat() . Вы должны переопределить его.

Комментарии:

1. вы можете удалить Cat::sleep , но реализовать DogCat::sleep путем вызова Cat::sleep , который, естественно, будет вызываться Animal::sleep по Cat пути.

Ответ №2:

Вы должны использовать виртуальное наследование

 class Animal
{
public:
    Animal(){}

    virtual void sleep()
    {
        cout << "zzzzzzzzz" << endl;
    }
    virtual void eat() = 0;

};

class Dog: virtual public Animal
{
protected:
    Dog(){}

    virtual void eat() override
    {
        cout << "eats dogfood" << endl;
    } 
};

class Cat : virtual public Animal
{
public:
    Cat(){}
    virtual void eat() override
    {
        cout << "eats catfood" << endl;
    }
};

class DogCat : public Dog, public Cat
{
public:
    DogCat(){}
    using Dog::eat;

};

int main(int argc, char** argv) {
    DogCat *DC = new DogCat();
    DC->sleep();//Error
}
  

Комментарии:

1. Это правильный ответ. Тем не менее, было бы неплохо немного подробнее рассказать OP, чтобы понять проблему diamond и почему у него есть двусмысленность (т.Е. Несколько экземпляров animal) и как ваше решение для виртуального наследования решает эту проблему.

2. @Christophe Это все еще не компилируется: coliru.stacked-crooked.com/a/07dafb58241bc191

3. Он работает для сна, но мне пришлось переопределить eat в DogCat, чтобы заставить его работать, в нем говорилось, что переопределение виртуальной функции «Animal:: eat» является неоднозначным и неоднозначным наследованием «void Animal:: eat (void)»

4. @BlackMoses Я просто посмотрел на ответ, но не просматривал код. Действительно, use Dog::eat; это просто псевдоним. В исходном операционном коде оба eat() существуют, и эта строка просто указывает, какой из них вызывать, если не используется явное разрешение области. Этот ярлык не работает с виртуальным наследованием, поскольку вам нужно не только указать, какой видимый псевдоним, но и выбрать единственный и единственный, который будет реализован в уникальной виртуальной базе, т. Е. virtual void eat() override { Dog::eat(); } , Как уже выяснил Никлас 😉