When Virtual Inheritance Is a Good Design

When virtual inheritance IS a good design?

If you have an interface hierarchy and a corresponding implementation hierarchy, making the interface base classes virtual bases is necessary.

E.g.

struct IBasicInterface
{
virtual ~IBasicInterface() {}
virtual void f() = 0;
};

struct IExtendedInterface : virtual IBasicInterface
{
virtual ~IExtendedInterface() {}
virtual void g() = 0;
};

// One possible implementation strategy
struct CBasicImpl : virtual IBasicInterface
{
virtual ~CBasicImpl() {}
virtual void f();
};

struct CExtendedImpl : virtual IExtendedInterface, CBasicImpl
{
virtual ~CExtendedImpl() {}
virtual void g();
};

Usually this only makes sense if you have a number of interfaces that extend the basic interface and more than one implementation strategy required in different situations. This way you have a clear interface hierarchy and your implementation hierarchies can use inheritance to avoid the duplication of common implementations. If you're using Visual Studio you get a lot of warning C4250, though.

To prevent accidental slicing it is usually best if the CBasicImpl and CExtendedImpl classes aren't instantiable but instead have a further level of inheritance providing no extra functionality save a constructor.

When is virtual inheritance a good idea?

Just because the user would add their own methods to a child class doesn't mean you need to use virtual inheritance. You would use it if, in your library, you have a single base class with multiple children, and people could inherit from multiple child classes at once (for example mixin rather than substitution).

Is there a downside to making ALL inheritance virtual?

Just what I found in the Standard:

  • A virtual base class is initialized by the most-derived type (see aschepler's answer).
  • You can't use a static_cast to convert to a derived class reference/pointer if there's virtual inheritance involved. [expr.static.cast]/2, 11
  • You can't use C-style casts ("Explicit type conversion (cast notation)") to convert to a derived class pointer/reference ([expr.cast]), at least the example in [expr.dynamic.cast]/9 says so. (oooh no C-style casts ;)
  • Copy/Move assignment and ctor cannot be trivial if there's a virtual base class. [class.copy]/12, 25
  • [class.copy]/28 "It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy assignment operator."
  • You can't have constexpr ctors if there's a virtual base class. [dcl.constexpr]/4
  • There are other subtleties, such as pointer-to-member conversions [conv.mem]/2, and reusing storage via placement-new on this [basic.life]/5,6.

Depending on the implementation of virtual base classes, there might be other drawbacks.

c++ why does virtual inheritance allow for the prevention of further inheritance?

If use virtual inheritance, the most-derived type has to do the initialization of this virtual base class. If you don't use virtual inheritance, the directly derived type has to do the initialization.

Therefore, the private ctor does not prevent the derived type NewClass from initializing the direct base class SealingClass, and AnotherClass does not have to initialize NewClass if it's not been virtually inherited.


Some examples:

template<typename Child>
class SealingClass {
public: // for now
SealingClass() {}
};

class NewClass : public SealingClass<T> {
public:
NewClass() : SealingClass<T>() {} // allowed, SealingClass<T> is a
// direct base class
};

class AnotherClass : public NewClass {
public:
AnotherClass() : NewClass() {} // allowed, NewClass is a
// direct base class
AnotherClass() : SealingClass<T>() {} // not allowed, SealingClass<T> is
// no direct nor a virtual base class
};

class NewClass_v : public virtual SealingClass<T> {
public:
NewClass_v() : SealingClass<T>() {} // allowed, SealingClass<T> is a
// direct base class
};

class AnotherClass_v : public NewClass_v {
public:
AnotherClass_v() : NewClass_v() {} // allowed, NewClass_virt is a
// direct base class
AnotherClass_v() : SealingClass<T>() {} // allowed, SealingClass<T> is a
// virtual base class
};

Now, if the ctor of SealingClass is private, AnotherClass_virt is not allowed to call this ctor due to the private access specifier and not being a friend.

If you leave out the explicit initialization of a base class (whether virtual or direct), it is default-initialized ([class.base.init]/8), that is, the default ctor is called implicitly (but you still must have access to the ctor, so it's the same as explicitly writting the call to the default ctor).


Some quotes:

[class.base.init]/1

In the definition of a constructor for a class, initializers for direct and virtual base subobjects and non-static data members can be specified by a ctor-initializer

[class.base.init]/7

A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.

[class.base.init]/10

In a non-delegating constructor, initialization proceeds in the following order:

  • First, and only for the constructor of the most derived class, virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
  • Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

Emphasis mine.

In C++, should I almost always use virtual inheritance?

The drawbacks are that

  1. All classes will have to initialize all its virtual bases all the time (e.g. if A is virtual base of B, and C derives from B, it also have to initialize A itself).
  2. You have to use more expensive dynamic_cast everywhere you use a static_cast (may or may not be the issue, depending on your system and whether your design requires it).

Point 1 alone makes it not worth it, since you can't hide your virtual bases. There is almost always a better way.

Why does virtual inheritance need to be specified in the middle of a diamond hierarchy?

I'm not sure of the exact reason they chose to design virtual inheritance this way, but I believe the reason has to do with object layout.

Suppose that C++ was designed in a way where to resolve the diamond problem, you would virtually inherit B and C in D rather than virtually inheriting A in B and C. Now, what would the object layout for B and C be? Well, if no one ever tries to virtually inherit from them, then they'd each have their own copy of A and could use the standard, optimized layout where B and C each have an A at their base. However, if someone does virtually inherit from either B or C, then the object layout would have to be different because the two would have to share their copy of A.

The problem with this is that when the compiler first sees B and C, it can't know if anyone is going to be inheriting from them. Consequently, the compiler would have to fall back on the slower version of inheritance used in virtual inheritance rather than the more optimized version of inheritance that is turned on by default. This violates the C++ principle of "don't pay what you don't use for," (the zero-overhead principle) where you only pay for language features you explicitly use.

Using virtual inheritance on final classes in unfinished class hierarchies

I would avoid virtual inheritance until actually needed. When you use virtual inheritance you are leaking part of the abstractions that you build on your class, and in particular how you initialize your base class, by forcing the call to the virtual base to the most derived type.

Virtual inheritance in C++ usages/tricks

The main point with virtual inheritance is to prevent derived classes from inheriting multiple copies of different superior classes. This can occur in any case where there may be multiple inheritance -- as you correctly note, the "diamond problem", which is to say where the inheritance graph is a DAG instead of a strict tree.

The C++ FAQ goes into it in some detail. I'd also recommend the C++ FAQ Book; I used to work for the authors and they're quite good.



Related Topics



Leave a reply



Submit