C++ virtual inheritance

Started by
11 comments, last by frob 8 years, 6 months ago

A simple question regarding virtual inheritance in C++. Given the following inheritance hierarchy:


struct A {
	A();	// make calling this a compile time error
	A(int) {}
	};

struct B : virtual public A {};

struct C : virtual public A, virtual public B {
	C() : A(5) {}
	};

Is there any way to make calling A::A() a compile time error? For example:


A a;     // error
B b;     // error
C c;     // valid 

Obviously I can throw a run-time error, but I'd prefer a compile time error.

Advertisement

You can make a function in struct A a pure virtual function and implement it in the child struct C.

You can make A and B's constructors protected.

You can make A and B's constructors protected.

Of course.. protected escaped my mind.

Thank-you for the responses, but its clear I was not clear. What I want to prevent is this:


struct D : virtual public A, virtual public B {
   D(){}
   };

D d;   // error here

The base class default constructor is only there to keep the other abstract classes placated, it should not ever actually be called. In this example A::A() should never be called. Now normally leaving the function declared but undefined (or more recently use = delete) is a fine solution, the program will compile fine and only fail to compile if the function is actually called. Problem is in this case, even though A::A() is never be called, its still required due to the compiler generated B::B(). Am I making any sense? Its probably too early to be posting about this...


The base class default constructor is only there to keep the other abstract classes placated, it should not ever actually be called. In this example A::A() should never be called. Now normally leaving the function declared but undefined (or more recently use = delete) is a fine solution, the program will compile fine and only fail to compile if the function is actually called. Problem is in this case, even though A::A() is never be called, its still required due to the compiler generated B::B(). Am I making any sense? Its probably too early to be posting about this...

So what you want is that A cannot be directly instantiated, while D can be?


A a; // should error
D d; // should compile

Because your mentioning of "A::A" should never be called is a little confusing. If you construct an object that inherits from A, the A ctor will have to be called. If you just don't want anyone to be able to instantiate A directly, hodgmans suggestion of setting it protected should do it:


class A
{
protected:
    A() {} // now only classes derived from A can call this
}

class D : virtual public A
{
}

A a; // this fails... 
D d; // ... but this compiles

Otherwise you need to clearly elaborate better what you want to do.

When using virtual inheritance, ie. in the hierarchy above, you only have one instance of the virtual class in the resulting object. So there is only one A, but it must be constructed in every derived class that inherits it (classes B, C, and D). Now if A (and B, C, and D by extension) all have default constructors, everything works cleanly. But say A doesn't have a default constructor, then B would have to (in its constructor) create A. So you pass the parameters to construct A to B along with any additional parameters, despite these parameters never being used because you have no intention of ever creating B, or creating A through B. As the hierarchy gets even slightly complex this deep parameter passing becomes a problem.

The easiest solution (apart from don't use MI) is to ensure all abstract base classes have a protected default constructor. This prevents them from accidentally being constructed, but they can easily be virtually inherited, and the final class then can call the appropriate non-default constructors for each virtual 'instance' as required. The only thing is sometimes you forget to call the appropriate non-default constructors for a base class. Since these default base class constructors aren't supposed to be default constructed, this would yield an invalid object. The solution I've always used was to simply throw an exception from the base class default constructors that aren't to be called and document appropriately. I was wondering if there was some way to turn that run-time error into a compile time one.

Trying to understand what you mean here..

You don't want to be able to instantiate class A using a default constructor but you also don't want the child classes to be able call the default constructor and so protected won't work?

If that is the case, then don't define a default constructor in class A and just have your non-default constructors protected instead.

Not going to answer your actual question (sounds like it's being covered), but rather point out that what you're trying to do sounds super... ungood. As in, I'd reject any code review that even hinted at half of what you're trying to do.

Virtual inheritance is a code smell. You almost certainly don't need it. If you think you need it, revisit your architecture. Anecdotally, in 20+ years of C++ programming I've used virtual inheritance only a handful of times, and all of those times I eventually realized I was being a goof and then switched the design to aggregation.

Second, protection controls on classes are a convenience to prevent casual/accidental misuse. It doesn't need to be (and actually can't be) absolute protection against a programmer who wants to break the rules. When you're designing an API, you aren't fighting a war against the other developers; you're supporting and enabling them to get their job done efficiently.

If you can't make C++ bend over backwards to do your fancy API design, your API is _too_ fancy, and you're probably working towards a misguided goal. smile.png

Sean Middleditch – Game Systems Engineer – Join my team!


struct C : virtual public A, virtual public B {
C() : A(5) {}
};

If your virtual base class stores values like that, you are almost certainly picking the wrong pattern for the job.

Virtual inheritance is really useful in a small number of cases. Except for those very few times, it is the wrong solution. Generally the only time it is useful is base classes that are purely utilitarian, mandatory within a wide number of classes, yet useless unless part of another class's functionality; the base must provide the functionality to a tree that requires multiple inheritance, yet must only have a single form of the utility it provides. It is an extremely uncommon requirement.

One of those very rare cases is the C++ ios_base class, inherited with virtual inheritance. It has IO flags constants (decimal, octal, hexadecimal, left-justified, right-justified, etc), locale information, and stream constants (good, bad, fail, eof). Note how these are pure utilities, they exist in all types of streams, and are otherwise useless without an actual stream. No matter the type of stream (input-only, output-only, input-and-output) it still must only have a single instance of these utilities. Having more than one instance of the utilities would be nonsensical; you cannot logically have two states one with "fail" the other with "EOF", or two sets of flags indicating both decimal and hexadecimal formats. In this very rare case where the design requires multiple inheritance and also shared behavior with a single base, virtual inheritance is needed.

Out of the tens of thousands of classes I've worked with over the years, the number of times virtual inheritance has been the right solution is something I can count on my fingers. It is a very rarely used solution, but in those rare cases it is important to have.

In your case where you demonstrate feeding a virtual parent class with a parameter, it shows you are not in the very rare situation where virtual inheritance is the right solution.

Since your class is specifying a parameter to the base, that means another class could also specify that type of parameter to the base, with the result that you are constructing the single base more than once. Attempting to construct it more than once won't do what you want for all but one of the places where it happens.

This topic is closed to new replies.

Advertisement