Sign in to follow this  
Ryan_001

C++ virtual inheritance

Recommended Posts

Ryan_001    3476

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.

Edited by Ryan_001

Share this post


Link to post
Share on other sites
Ryan_001    3476

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...

Share this post


Link to post
Share on other sites
Juliean    7077

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.

Edited by Juliean

Share this post


Link to post
Share on other sites
Ryan_001    3476

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.

Share this post


Link to post
Share on other sites
Syntac_    1355

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.

Share this post


Link to post
Share on other sites
frob    44972

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.  

Share this post


Link to post
Share on other sites
Bregma    9214

What is your class invariant?

 

I can't see any possible way to have a good architecture in which you have a virtual base class with differing invariants down different inheritance paths.

Share this post


Link to post
Share on other sites
xeyedmary    163
 

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

 

Ok, I don't nessessarily disagree with you, and certainly readable code is the holy grail of any developer, lord knows a mess of virtual classes handily enables this. But what about the case of abstraction subtyping? Seems like virtual inheritance is the right tool for the job in that case. How do you handle it using composition?

Share this post


Link to post
Share on other sites
frob    44972
You use regular, normal, non-virtual inheritance for most of that.

Nearly all abstractions are handled through regular plain inheritance. Want to add an interface (pure virtual base class)? Add through inheritance? Want to add another abstraction? Add it through normal non-virtual multiple inheritance.

As a general guide -- which languages like Java and C# enforce through policy -- it is fine to inherit functionality through one class, but after that add interfaces, which in c++ is a class with all pure virtual functions.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this