Сценарий Diamond-inheritance прекрасно компилируется в g++, но создает предупреждения/ошибки в VС++/Eclipse

У меня есть базовый класс Base, который является чистым виртуальным классом:

class Base {

public:
    virtual void A() = 0;
    virtual void B() = 0;

    virtual ~Base() { } // Eclipse complains that a class with virtual members must have virtual destructor
};

У меня также есть 2 других класса, один из которых реализует A(), а другой реализует B():

class DerivedA : public virtual Base
{
public:
    virtual void A() {
        printf("Hello from A");
    }
};

class DerivedB : public virtual Base
{
public:
    virtual void B() {
        printf("Hello from B");
    }
};

Виртуальное ключевое слово в декларации должно решить проблему с алмазом.

Теперь я хотел бы объединить два класса в другой класс, чтобы реализовать как A(), так и B():

class DerivedC: public DerivedA, public DerivedB {
     // Now DerivedA and DerivedB are combined
};

// Somewhere else in the code
DerivedC c;
c.A();
c.B();

Проблема: Хотя g++ компилирует код просто отлично, Eclipse дает ошибку: The type 'DerivedC' must implement the inherited pure virtual method 'Base::B'. При компиляции с визуальной студией я получаю 2 предупреждения:

warning C4250: 'DerivedC' : inherits 'DerivedB::DerivedB::B' via dominance
warning C4250: 'DerivedC' : inherits 'DerivedA::DerivedA::A' via dominance

Итак, вопрос:, как правильно это сделать? Вызывает ли код выше поведение undefined?

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

Ответы

Ответ 1

Каков правильный способ сделать это? Вызывает ли код выше поведение undefined?

Код отлично действует. Здесь нет undefined Поведение.
Неквалифицированный вызов A() через объект класса DerivedC всегда будет вызывать DerivedA::A(), а неквалифицированный вызов B() через объект класса DerivedC всегда будет вызывать экземпляр DerivedB::B().

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

Обратите внимание, что стандарт С++ не ограничивает компиляторы от выдачи информационных предупреждений для совершенно правильного кода. В документации для предупреждения C4250 приведен пример, который говорит вам, почему Visual С++ решает дать это предупреждение.

Ответ 2

Возможно, вы захотите попробовать следующее:

class DerivedC: public DerivedA, public DerivedB {
public:
    using DerivedA::A;
    using DerivedB::B;
};

Я не могу тестировать сам Eclipse или VС++...

Ответ 3

Я не знаю, почему компилятор будет жаловаться на все это; это просто стандартная техника mixin. Классы Base, DerivedA и DerivedB являются абстрактными и не могут быть созданы, но обычно случай для миксинов. Весь смысл микса заключается в том, что он не реализовать весь интерфейс. И DerivedC реализует как A() и B() через свои унаследованные элементы.

Если компилятор отказывается компилировать этот код, он прерывается.

Что касается предупреждений... компилятор может предупредить обо всем, что угодно:

  • Нет требования, чтобы класс с виртуальными членами виртуальный деструктор. На практике это обычно хорошая идея, однако (если деструктор не защищен), а предупреждение компилятора необходимо.

  • Предупреждения из Visual Studio являются "информативными", я думаю, но это это способ, которым язык предназначен для работы. И это, конечно, не что-то избегать. В этом отношении я не думаю, что господство на самом деле играет здесь роль, поскольку функции из Base являются чистыми виртуальная. Кажется, что Visual Studios пытается сказать, что в DerivedC, фактическая перегрузка A() равна DerivedA::A(), а не Base::A(). Кажется, это то, что интуитивно ожидало бы от меня; правила, касающиеся доминирования, на самом деле являются лишь формальным утверждением что интуитивно ожидало бы.

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

Ответ 4

Известно, что Visual Studio имеет ошибку компилятора, выдающую предупреждение C4250 в ситуациях, когда доминируемая функция является чистой виртуальной. Ошибка была закрыта как "Не исправлена"; принятое решение - подавить предупреждение, используя:

#pragma warning( disable: 4250 ) /* 'class1' : inherits 'class2::member' via dominance */

См. также обсуждение http://msdn.microsoft.com/en-us/library/6b3sy7ae(VS.80).aspx#CommunityContentHeader.

Ответ 5

Ваш базовый класс является абстрактным: он не может быть создан. Класс B и A также являются абстрактными, поскольку они реализуют только один метод.

Два решения: в файле DerivedC.cpp

void DerivedC::A(){ 
   DerivedA::A();
}
void DerivedC::B(){
   Derived:B();
}

Или вы можете использовать ключевое слово using в своем заголовочном файле:

class DerivedC: public DerivedA, public DerivedB {
public:
    using DerivedA::A;
    using DerivedB::B;
};