Предотвращать множественное наследование в С++

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

using namespace std;

class Base1
{
    public:
    Base1() 
    {
        cout << "Base1 constructor..." << endl;
    }

    ~Base1() 
    {
        cout << "Base1 Destructor..." << endl;
    }
};  

class Base2
{
public:
    Base2() 
    {
        cout << "Base2 constructor..." << endl;
    }

    ~Base2() 
    {
      cout << "Base2 Destructor..." << endl;  
    }
};

class Derived : public Base1, public Base2 
{
public:
  Derived()
  {
      cout << "Derived constructor...."  << endl;
  }
  ~Derived()
  {
      cout << "Derived Destructor..." << endl;
   }
};


int main()
{
   cout << "Hello World" << endl; 
   Base1 b1; Base2 b2;
   Derived d1;

   return 0;
}

Описание. Существует два базовых класса с именем Base1 и Base2 и один производный класс с именем Derived. Производное несколько унаследовано от Base1 и Base2.

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

  • Scenerio 1: класс Derived: public Base1//Ok.--- > No Error
  • Scenerio 2: класс Derived: public Base2//Ok.--- > No Error
  • Scenerio 3: класс Derived: public Base1, public Base2//Ошибка или исключение или что-то еще. Не удалось наследовать.

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

Спасибо.

Ответы

Ответ 1

Объявить чистую виртуальную функцию в обоих базовых системах с другим типом возврата:

class B1 {
   virtual void a() = 0;
};

class B2 {
   virtual int a() = 0; // note the different return type
};

Невозможно наследовать от обоих.

class D : public B1, public B2 {
public:
    // virtual void a() {} // can't implement void a() when int a() is declared and vice versa
    virtual int  a() {}
};

int main(void) {
    D d; // produces C2555 error
    return 0;
}

Эта ошибка возникает:

  • ошибка C2555: 'D:: a': переопределение типа возвращаемой виртуальной функции отличается и не является ковариантным из 'B1:: a'
  • см. объявление 'B1:: a'

Ответ 2

Вы можете сделать это с помощью С++ 11, посмотрите:

#include <type_traits>
struct Base1 {}; 
struct Base2 {}; 

struct Derived 
   : public Base1
  , public Base2
{ 
  Derived() 
  { 
     static_assert( !(std::is_base_of< Base1, Derived >::value && std::is_base_of< Base2, Derived >:: value), "You cannot inherit from both Base1 and Base2" ); 
  } 
}; 



int main() { Derived d; return 0; }

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

Ответ 3

Проверка времени выполнения:

#include <iostream>
#include <stdexcept>
#include <typeinfo>

class B1 {
    protected:
    template <typename D>
    B1(D* d) {
        // The paranoid 'is_same' is preventing the cast (B1*)this in the derived constructor.
        if((void*)this != (void*)d || std::is_same< B1, D >::value)
            throw std::logic_error("Multiple Inheritance [1]");
    }

    int data;
};

class B2 {
    protected:
    template <typename D>
    B2(D* d) {
        // The paranoid 'is_same' is preventing the cast (B2*)this in the derived constructor.
        if((void*)this != (void*)d || std::is_same< B2, D >::value)
            throw std::logic_error("Multiple Inheritance [2]");
    }

    int data;
};


struct D : public B1, public B2 {
    D()
    :   B1(this), B2(this)
    {}
};

int main() {
    D d;
}

Примечание. Это не работает, если классы пустые. Не так хорошо, как отличная идея @egur. Тем не менее, это дает возможность не вводить виртуальные функции.

Ответ 4

Используйте метод, предложенный Лай Ю-Сюань, но дайте Base0 чистую виртуальную функцию, которая должна быть переопределена Base1 и Base2. Компилятор должен жаловаться на неоднозначную функцию, если вы пытаетесь наследовать как Base1, так и Base2. Редактировать: это не правильный ответ, как указал Майк ниже

Ответ 5

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

class Base1 : public Base0
class Base2 : public Base0

Затем, если пользователь пытается наследовать от Base1 и Base2, компилятор будет жаловаться.