Main Content

Missing virtual inheritance

A base class is inherited virtually and nonvirtually in the same hierarchy

Description

This defect occurs when:

  • A class is derived from multiple base classes, and some of those base classes are themselves derived from a common base class.

    For instance, a class Final is derived from two classes, Intermediate_left and Intermediate_right. Both Intermediate_left and Intermediate_right are derived from a common class, Base.

  • At least one of the inheritances from the common base class is virtual and at least one is not virtual.

    For instance, the inheritance of Intermediate_right from Base is virtual. The inheritance of Intermediate_left from Base is not virtual.

Risk

If this defect appears, multiple copies of the base class data members appear in the final derived class object. To access the correct copy of the base class data member, you have to qualify the member and method name appropriately in the final derived class. The development is error-prone.

For instance, when the defect occurs, two copies of the base class data members appear in an object of class Final. If you do not qualify method names appropriately in the class Final, you can assign a value to a Base data member but not retrieve the same value.

  • You assign the value using a Base method accessed through Intermediate_left. Therefore, you assign the value to one copy of the Base member.

  • You retrieve the value using a Base method accessed through Intermediate_right. Therefore, you retrieve a different copy of the Base member.

Fix

Declare all the intermediate inheritances as virtual when a class is derived from multiple base classes that are themselves derived from a common base class.

If you indeed want multiple copies of the Base data members as represented in the intermediate derived classes, use aggregation instead of inheritance. For instance, declare two objects of class Intermediate_left and Intermediate_right in the Final class.

Examples

expand all

#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;
}

In this example, Final is derived from both Intermediate_left and Intermediate_right. Intermediate_left is derived from Base in a non-virtual manner and Intermediate_right is derived from Base in a virtual manner. Therefore, two copies of the base class and the data member m_b are present in the final derived class,

Both derived classes Intermediate_left and Intermediate_right do not override the Base class methods get and set. However, Final overrides both methods. In the overridden get method, it calls Base::get through Intermediate_left. In the overridden set method, it calls Base::set through Intermediate_right.

Following the statement d.set(val), Intermediate_right’s copy of m_b is set to 12. However, Intermediate_left’s copy of m_b is still zero. Therefore, when you call d.get(), you obtain a value zero.

Using the printf statements, you can see that you retrieve a value that is different from the value that you set.

The defect appears in the final derived class definition and on the name of the class that are derived virtually from the common base class. Following are some tips for navigating in the source code:

  • To find the definition of a class, on the Source pane, right-click the class name and select Go To Definition.

  • To navigate up the class hierarchy, first navigate to the intermediate class definition. In the intermediate class definition, right-click a base class name and select Go To Definition.

Correction — Make Both Inheritances Virtual

One possible correction is to declare both the inheritances from Base as virtual.

Even though the overridden get and set methods in Final still call Base::get and Base::set through different classes, only one copy of m_b exists in 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;
}

Result Information

Group: Object oriented
Language: C++
Default: Off
Command-Line Syntax: MISSING_VIRTUAL_INHERITANCE
Impact: Medium

Version History

Introduced in R2015b