C++ lets a single class inherit from more than one base at the same time. This is multiple inheritance, a feature most other mainstream object-oriented languages either restrict or refuse outright. C++ keeps it because, used carefully, it composes small, reusable behaviors (logging, serialization, discount handling) into a single type without the boilerplate of wrapping or delegating. This lesson covers the syntax, the construction order, the ambiguity problems that come with it, the scope-resolution syntax that fixes them, the memory layout that explains why casting between the bases changes the pointer's address, and the mixin pattern, which is the one use case that almost everyone agrees is legitimate.
A class declares multiple bases by listing them after the colon, separated by commas, each with its own access specifier.
Product now inherits from two unrelated classes at once. It picks up log() from Loggable and serialize() from Serializable, and adds its own name and price fields. From the outside, calling mouse.log(...) looks identical to calling a method Product defined itself.
The pieces of the declaration:
class Product is the derived class being defined.: public Loggable, public Serializable is the base list. Each entry has its own access specifier (public, protected, or private), and the access specifier must be repeated for every base. Writing : public Loggable, Serializable makes Serializable private by default for class (and public by default for struct), which is almost never the intent. Spell it out every time.There's no language limit on the number of bases, but anything past three is a sign the design is in trouble.
This is the standard "two arrows in" picture seen in any UML reference. The diamond head sits on each base, pointing toward Product, which is the derived class. Product has two parents that know nothing about each other.
When an object of a class with multiple bases is created, the bases are constructed first, left-to-right in the order they appear in the base list, not in the order their initializers appear in the constructor's member-initializer list. Then the derived class's own constructor body runs. Destructors run in the reverse order: the derived class first, then the bases right-to-left.
Read the output top to bottom. Loggable is the first base in the list, so it's constructed first. Serializable is next. Once both base subobjects exist, the derived Product constructor runs. At end of main, p goes out of scope and the destructors fire in the exact reverse order: derived first, then the bases from right to left.
The order is fixed by the base list, not by the member-initializer list. Writing the constructor as:
still constructs the bases in the order Loggable, then Serializable, because that's the order the base list declares. Most compilers warn about the mismatch. With g++:
The fix is to write the initializer list in the same order as the base list. It's a stylistic and a correctness-of-intent issue: when the initializer order doesn't match construction order, code can assume a base is ready when it isn't.
Construction order is a compile-time decision baked into the generated code. There's no runtime cost to multiple bases beyond the bytes each base subobject occupies. The constructors run back-to-back, like inlined function calls.
The trouble starts when two bases happen to have a member with the same name. The compiler doesn't have a rule for picking one, so it refuses to guess and reports an error.
This does not compile. Both Loggable and Discountable define print(), and Product inherits both, so the call mouse.print() is ambiguous. With g++ the message is:
The compiler found two candidates and refused to pick. The same kind of error happens for data members: if both bases have a field called id, then mouse.id is also ambiguous.
A common surprise is that the ambiguity is detected at the lookup stage, not at the call stage. Even if the two functions had different signatures (one took an int, one took nothing), the lookup would still find both names and stop. The compiler doesn't try overload resolution across multiple bases.
The fix is to disambiguate explicitly.
The way to call a specific base's member is to qualify the name with the base class and the scope-resolution operator ::.
mouse.Loggable::print() reads as "on the object mouse, call the print member that lives in Loggable". The same form works inside Product's own member functions, where the receiver is the implicit this.
Inside printAll, the bare name print would still be ambiguous (the same two candidates show up), so each call needs the Base:: prefix to pick a side.
A second, cleaner option is to add a using declaration in the derived class to pull one specific base's member into scope as the unqualified name. That removes the ambiguity for callers, at the cost of declaring which print is meant:
After that, mouse.print() compiles and calls Loggable::print. mouse.Discountable::print() is still available for the other one. The using-declaration approach is most useful when one of the two print functions is the obvious default for the derived class and callers benefit from a clean one-word call.
Base::name always works. The unqualified name works only when lookup finds exactly one candidate. When in doubt, qualify it.
A multiple-inheritance object isn't magic. The derived object contains one subobject for each base, laid out in the same order as the base list, followed by the derived class's own data members. Each base subobject is a fully-formed instance of that base class, sitting at its own offset inside the bigger derived object.
Sample output (typical 64-bit Linux, g++):
Two things stand out. First, sizeof(Product) is 24, not 4 + 8 + 4 = 16. The compiler inserts padding so that Discountable's double lands on an 8-byte boundary, and the trailing int stock adds 4 bytes plus 4 more bytes of tail padding so an array of Product keeps every element 8-byte aligned.
Second, the three pointers don't all hold the same address. Loggable* and Product* point at the start of the object (offset 0), because Loggable is the first base. Discountable* points 8 bytes deeper into the same object, because the Discountable subobject sits after the Loggable one. The compiler adjusts the pointer value when casting between the derived type and a non-leading base.
The diagram shows all three pointers reaching into the same single object at different starting addresses. The compiler does the offset arithmetic whenever a cast happens, including the implicit casts that happen when passing a Product* to a function expecting a Discountable*.
This adjustment is silent and correct, but it has two consequences to keep in mind:
== after casting between unrelated bases of the same object can give "different" answers because the addresses really are different.Discountable* back to Product* with a static_cast works only because the compiler knows the offset to subtract. A reinterpret_cast skips the adjustment and produces a broken pointer. This is one reason raw casts in code with multiple inheritance are risky.Casting a derived pointer to a base subobject pointer is one integer addition (or subtraction) at runtime when the offset is non-zero. For the leading base, the compiler doesn't need to adjust at all. This cost is negligible for normal code, but it is the reason reinterpret_cast is unsafe across multiple inheritance.
The one use of multiple inheritance that almost every C++ style guide tolerates is the mixin pattern. A mixin is a small class that adds a single, focused capability (logging, serialization, discount handling, equality) to whatever class inherits from it. Mixins typically have no data of their own, or just one or two fields, and they don't model an "is-a" relationship in the domain. They model a "can-do" capability.
The pattern works because mixins are designed not to clash with each other. They use distinct method names, hold no overlapping data, and don't try to be standalone types.
A worked example. Loggable adds a log method, Discountable adds discount-rate handling, and Product mixes both into a domain type that already has its own name and price.
A few things this example shows about why mixins compose cleanly:
Loggable::log and Discountable::setDiscount/applyDiscount are deliberately distinct, so calls like mouse.log(...) are unambiguous without scope-resolution syntax.Loggable has no data members, so it adds essentially nothing to the object's size. Discountable adds one double. The cost of mixing them in is the cost of those bytes plus their methods.Loggable doesn't depend on Discountable or vice versa. They can be mixed independently or together.The same Discountable mixin can be added to a different domain class:
Both Product and Cart get the same two capabilities, written once. That's the practical payoff of mixins. Without multiple inheritance, the choice would be copy-pasting the code into both classes or making Loggable and Discountable member fields and writing forwarding calls in every container class. Multiple inheritance lets the capability sit at the language level instead of behind a wrapper.
The discipline that makes mixins safe is small:
Loggable is fine, Customer is not.A real ambiguity bug, the kind that shows up when two well-meaning mixins happen to define a common method name.
Compile this with g++ -std=c++17 example.cpp and the build fails with something like:
Both Loggable and Discountable define describe, and Product inherits both. The compiler can't decide which one mouse.describe() should call, so it refuses to compile.
Two reasonable fixes exist; the right one depends on the intent.
Fix 1: Disambiguate at the call site. To call both, name them explicitly:
Fix 2: Override `describe` in `Product` and call both bases explicitly. This is cleaner when Product should always describe itself in a particular composite way:
Output of fix 2:
Ambiguity errors in multiple inheritance are usually a signal to rethink either the names in the mixins or the design of the derived class. The compiler refusing to guess is an asset, not an obstacle.
Multiple inheritance has a real cost, and many teams either ban it outright or restrict it to mixins. The reasons matter, so the limits feel like guidance rather than folklore.
| Concern | Why it matters |
|---|---|
| Name ambiguity | Adding a new method to a base can suddenly break unrelated code that uses the derived class. |
| Confusing pointer arithmetic | Casting between bases changes the address of the same object, which can surprise readers. |
| Constructor and destructor order | The order is fixed by the base list, not the initializer list, and it's easy to assume otherwise. |
| Common ancestors | If two bases share a common base, the derived object ends up with two copies of that base unless virtual inheritance is used. |
| Tooling and debugging | Stack traces and debugger views become harder to follow when the same object has multiple "this" pointers, one per base. |
| Cognitive load for readers | Single inheritance is common; multiple inheritance is rare. Designs with multiple inheritance take longer to read. |
Major C++ style guides reflect this. The Google C++ Style Guide allows multiple inheritance only when all but one of the bases have no state (are essentially mixins). The C++ Core Guidelines push the same direction, recommending interfaces (abstract classes with no data) when multiple bases are needed. The pattern that survives all of these rules is mixins, which is why this lesson spent so much time on them.
The signal of trouble is when two of the bases start to overlap: same field name, same method name, or shared ancestor. At that point the design wants to be reshaped, often into composition (holding mixins as members and forwarding to them) or into a single base with a richer interface.
10 quizzes