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:
- When virtual inheritance IS a good design?
- http://www.parashift.com/c++-faq-lite/virtual-inheritance-abcs.html
- http://www.artima.com/intv/abcs.html
- http://cpptips.com/virt_inher2
- http://g.oswego.edu/dl/mood/C++AsIDL.html
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).