17

I need to write a coding convention that will be used both by newbies and experienced C++ developers. The rule on inheritance for dynamic polymorphism goes like this:

  • For dynamic polymorphism, consider using single inheritance (tree-like hierarchy), possibly with multiple inheritance of abstract interfaces
  • for inheritance along the hierarchy (base classes, etc.), by default, use public inheritance
  • for inheritance of abstract interface, by default, use public virtual inheritance

This rule will be followed by a detailed information about implementation, possible exceptions, etc.

So, the question: Is this rule desirable for both newbies and experienced C++ developers? (pros/cons, as well as sources and links are welcome)


The ones I see are:

Pros:

  • rule easily usable by newbies, without restricting experienced developpers.
  • familiar to those already familiar with Java/.NET's interfaces
  • dodges the problems related with virtual inheritance of implementation (as it is reserved for abstract interfaces), as well as non-virtual inheritance (possible ambiguity when casting to the interface class)

Cons:

  • slight performance cost (speed when casting to the interface, size of the virtual tables, additional pointer(s) in class instance)

Note: I've read the following online sources:

Note 2: The use of the "abstract interface" name is coined after Sutter & Alexandrescu's use in item 36 of "C++ Coding Standards"


This is one case that should work (its Java/C# equivalent using interfaces just work), but that doesn't in C++ if the interface inheritance is not virtual:

class A
{
   public :
      virtual ~A() = 0 {}
} ;

class B : public A {} ; // should have been virtual to avoid the error
class C : public A {} ; // should have been virtual to avoid the error

class D : public B, public C
{
   public : 
      virtual ~D() {}
} ;

void foo(A * c) {}
void bar(D * d)
{
   foo(d) ; // Error: ambiguous conversions from 'D *' to 'A *
}

And yes, explicit casting to remove the ambiguity is the wrong solution (explicit casting is usually the wrong solution anyway).

Community
  • 1
  • 1
paercebal
  • 81,378
  • 38
  • 130
  • 159
  • 1
    Seems fine to me. I would also add one rule: *"Avoid OO polymorphism if possible. Try alternatives such as [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) and [ad-hoc polymorphism using type-erasure](http://stackoverflow.com/questions/18856824/ad-hoc-polymorphism-and-heterogeneous-containers-with-value-semantics)"* – Nawaz Dec 02 '13 at 12:16
  • 2
    I'd say that implementation inheritance should usually be private (exposing members only when necessary), and only done at all if there's a good reason not to prefer composition. Interface inheritance should usually be non-virtual, since the derived class is usually final. In general, keep inheritance as simple as possible (but no simpler). – Mike Seymour Dec 02 '13 at 13:02
  • 1
    @MikeSeymour : I corrected the rule. I was thinking about choosing the inheritance tree with the design (for example, if you're building a GUI hierarchy, keep the main tree about GUI only [A TextBox is-a Control, which is-a Widget], using public single inheritance, and keep non-GUI features [clonability, toStringability, serializability, etc.] as abstract interface through public virtual inheritance). Of course, implementation is a "implementation detail", so in that case, composition should be chosen before private inheritance. – paercebal Dec 02 '13 at 13:29
  • I would not tolerate a 'slight performance hit' just so that you can compare the object pointers by means of the interface baseclass, i think it is not worth it. generally C++ is already full of 'slight performance hits' see exceptions, see rtti (though it is possible to disable exceptions and rtti during compilation) – MichaelMoser Dec 04 '13 at 08:45
  • 2
    @user3034482 : Comparing base class pointers? What for? I never mentioned that in my question, did I? Anyway, are you still fighting over micro-optimizations? What's the performance hit of one pointer when your struct is 500 bytes large or larger? What's the performance hit of one virtual function call when it does a lengthy sort what could span 1ms or more? Disabling the RTTI or exceptions? Please, we're in 2013, not in 1990... If I wanted performance, I would remove all the qsort calls and void * polluting the code and replace then with std::sort and functors. This is not the point, here. – paercebal Dec 04 '13 at 08:56
  • consider adding: avoid inheriting implementation and interface from the same base class. – Alexander Oh Dec 05 '13 at 21:04
  • Java's base class + interface model is pretty good to me, and makes hierarchic structure of inheritance. – SwiftMango Dec 06 '13 at 00:23
  • Q: comparing base class pointers? A: virtual base classes are good for solving the circular inheritance problem; for inheriting interfaces they are only good for disambiguating base class pointer of the interface. Q: Microoptimizations? A: depends on what you do; yes; things like that add up; especially if there is no reason for it in the first place. – MichaelMoser Dec 06 '13 at 05:02
  • Also please look at the Google C++ style guide - they don't do rtti and exceptions; its for good reasons. – MichaelMoser Dec 06 '13 at 05:10
  • 5
    @user3034482 : `please look at the Google C++ style guide` : I did. In fact a lot of Top 1% people at StackOverflow did, and their comments were not really positive on that. The "Google C++ Style reference" is not really a C++ reference and is more a "if you're a newbie, and don't want to learn, please use that subset, thank you". If you want references for C++, please use serious documents, like Sutter & Alexandrescu's C++ coding Standards, or take a look at the F-35 "JSF C++ Coding Standards". – paercebal Dec 06 '13 at 09:20
  • 3
    @user3034482 : `they don't do rtti and exceptions; its for good reasons`. For the RTTI (the rule is "avoid", there is no mention of efficiency, only design). As for the exception, this is a decision based on the fact Google's code is not exception safe, which is a polite way to say it easily leaks memory and resources, so more a testament to the state of the code than anything else. Again, efficiency is barely mentioned. – paercebal Dec 06 '13 at 09:28
  • @user3034482 : `for inheriting interfaces they are only good for disambiguating base class pointer of the interface.` : Exactly. Casting a derived class into an interface it implements should be implicit. Without virtual inheritance, if the interface is inherited multiple times, the cast is not implicit anymore and needs an explicit cast for disambiguation. This is this explicit cast I want to avoid. Note that I fail to see how this is related to comparing pointers, or as you put it: `so that you can compare the object pointers by means of the interface baseclass`. – paercebal Dec 06 '13 at 09:35
  • @user3034482 : `Q: Microoptimizations? A: depends on what you do;` Welcome to my world. I'm happy we finally agree my performance problems are not the same as yours. – paercebal Dec 06 '13 at 09:42
  • You could clarify and say for implementing the interface where an interface is a pure virtual class. – Damian Apr 21 '14 at 20:55
  • 1
    I disagree on your rule "for inheritance of abstract interface, by default, use public virtual inheritance." In all of my career, I've only had to use virtual inheritance only a handful of time. In a lot of cases, it could be avoided through better design or re-factoring. Making it default imposes a performance penalty and goes against the C++ philosophy of "Pay for only what you use." It should be up to the judgement of the designer whether to use virtual inheritance or not. – Nathan Doromal Apr 21 '14 at 22:53

3 Answers3

3

You know what? You already give all the important info in the question. I don't see anything to answer on the technical level. And apparently noone else saw any significant technical problem with what you posted either.

I'll answer your bolded question though: Yes, it is suitable for both newbs and pros.

  • Newbs have some useful technical guidelines.
  • Pros can do what they want if they can give a rationale, because you qualify your rules with "consider" and "by default", so basically noone else can say you have to do so or so because of the style rules, because your rules' phrasing already allow for exceptions.
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
1

Your inheritance example does not work if it is not virtual because of the classic multiple inheritance problem in c++, the diamond of death. Basically if you do not specify virtual inheritance each parent class (B,C) have their own base A objects. This makes all access to non static base class functions and variables (conversion as well im assuming) ambiguous. I can't imagine a situation in which you could avoid this in c++.

Edit: For the record this is the working code:

class A
{
public :
    virtual ~A() {}
};

class B : virtual public A {};
class C : virtual public A {};

class D : virtual public B, virtual public C
{
public :
    virtual ~D() {}
};

void foo(A * c) {}
void bar(D * d)
{
    foo(d);
}

int main(void)
{
    D d;
    foo(&d);
    return 0;
}
h4unt3r
  • 846
  • 10
  • 14
1

Your first rule excludes class D at all:

For dynamic polymorphism, consider using single inheritance (tree-like hierarchy), possibly with multiple inheritance of abstract interfaces

Your rules are just fine.

So, what is your question at all? What is remaining now?

Frunsi
  • 7,099
  • 5
  • 36
  • 42