Пропавшие без вести виртуального наследования

Базовый класс наследован фактически и нефактически в той же иерархии

Описание

Отсутствие виртуального наследования происходит когда:

  • Класс выведен от нескольких базовых классов, и некоторые из тех базовых классов самостоятельно выведены от общего базового класса.

    Например, класс Final выведен от двух классов, Intermediate_left и Intermediate_right. И Intermediate_left и Intermediate_right выведены от общего класса, Base.

  • По крайней мере одним из наследований от общего базового класса является virtual, и по крайней мере один не virtual.

    Например, наследованием Intermediate_right от Base является virtual. Наследованием Intermediate_left от Base не является virtual.

Риск

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

Например, когда дефект происходит, две копии элементов данных базового класса появляются в объекте класса Final. Если вы не квалифицируете имена методов соответственно в классе Final, можно присвоить значение элементу данных Base, но не получить то же значение.

  • Вы присваиваете значение с помощью метода Base, к которому получают доступ через Intermediate_left. Поэтому вы присваиваете значение одной копии участника Base.

  • Вы получаете значение с помощью метода Base, к которому получают доступ через Intermediate_right. Поэтому вы получаете различную копию участника Base.

Фиксация

Объявите все промежуточные наследования как virtual, когда класс выведен от нескольких базовых классов, которые самостоятельно выведены от общего базового класса.

Если вы действительно хотите несколько копий элементов данных Base, как представлено в промежуточных производных классах, используйте агрегацию вместо наследования. Например, объявите два объекта класса Intermediate_left и Intermediate_right в классе Final.

Примеры

развернуть все

#include <stdio.h>
class Base {
public:
    explicit Base(int i): m_b(i) {};
    virtual ~Base() {};
    virtual int get() const {
        return m_b;
    }
    virtual void set(int b) {
        m_b = b;
    }
private:
    int m_b;
};

class Intermediate_left: virtual public Base {
public:
    Intermediate_left():Base(0), m_d1(0) {};
private:
    int m_d1;
};

class Intermediate_right: public Base {
public:
    Intermediate_right():Base(0), m_d2(0) {};
private:
    int m_d2;
};

class Final: public Intermediate_left, Intermediate_right {
public:
    Final(): Base(0), Intermediate_left(), Intermediate_right() {};
    int get() const {
        return Intermediate_left::get();
    }
    void set(int b) {
        Intermediate_right::set(b);
    }
    int get2() const {
        return Intermediate_right::get();
    }
};

int main(int argc, char* argv[]) {
    Final d;
    int val = 12;
    d.set(val);
    int res = d.get();
    printf("d.get=%d\n",res);             // Result: d.get=0
    printf("d.get2=%d\n",d.get2());       // Result: d.get2=12
    return res;
}

В этом примере Final выведен и от Intermediate_left и от Intermediate_right. Intermediate_left выведен от Base non-virtual способом, и Intermediate_right выведен от Base способом virtual. Поэтому две копии базового класса и элемента данных m_b присутствуют в итоговом производном классе,

Оба производных класса Intermediate_left и Intermediate_right не заменяют методы класса Base get и set. Однако Final заменяет оба метода. В замененном методе get это вызывает Base::get через Intermediate_left. В замененном методе set это вызывает Base::set через Intermediate_right.

После оператора d.set(val) копия Intermediate_right m_b установлена в 12. Однако копия Intermediate_left m_b является все еще нулевой. Поэтому, когда вы вызываете d.get(), вы получаете нуль значения.

Используя операторы printf, вы видите, что получаете значение, которое отличается от значения, которое вы устанавливаете.

Дефект появляется в итоговом определении производного класса и на имени класса, которые выведены фактически от общего базового класса. Следующее является некоторыми советами для навигации в исходном коде:

  • Чтобы найти определение класса, на панели Source, щелкают правой кнопкой по имени класса и выбирают Go To Definition.

  • Чтобы переместиться по иерархии классов, сначала перейдите к промежуточному определению класса. В промежуточном определении класса щелкните правой кнопкой по имени базового класса и выберите Go To Definition.

Исправление — делает оба наследования виртуальными

Одно возможное исправление должно объявить обоих наследования от Base как virtual.

Даже при том, что замененный get и методы set в Final все еще вызывают Base::get и Base::set через различные классы, только одна копия m_b существует в Final.

#include <stdio.h>
class Base {
public:
    explicit Base(int i): m_b(i) {};
    virtual ~Base() {};
    virtual int get() const {
        return m_b;
    }
    virtual void set(int b) {
        m_b = b;
    }
private:
    int m_b;
};

class Intermediate_left: virtual public Base {
public:
    Intermediate_left():Base(0), m_d1(0) {};
private:
    int m_d1;
};

class Intermediate_right: virtual public Base {
public:
    Intermediate_right():Base(0), m_d2(0) {};
private:
    int m_d2;
};

class Final: public Intermediate_left, Intermediate_right {
public:
    Final(): Base(0), Intermediate_left(), Intermediate_right() {};
    int get() const {
        return Intermediate_left::get();
    }
    void set(int b) {
        Intermediate_right::set(b);
    }
    int get2() const {
        return Intermediate_right::get();
    }
};

int main(int argc, char* argv[]) {
    Final d;
    int val = 12;
    d.set(val);
    int res = d.get();
    printf("d.get=%d\n",res);             // Result: d.get=12
    printf("d.get2=%d\n",d.get2());       // Result: d.get2=12
    return res;
}

Информация о результате

Группа: Объектно-ориентированный
Язык: C++
Значение по умолчанию: 'off'
Синтаксис командной строки: MISSING_VIRTUAL_INHERITANCE
Влияние: носитель

Введенный в R2015b