Build a Copier from a Scanner and a Printer that both derive from Device, and the Copier contains two Device objects: one with id 1, one with id 2. Add virtual to the two base declarations and it contains one, with id 3, constructed by a constructor that Scanner and Printer never get to run, and the object grows from 8 bytes to 24. Multiple inheritance in C++ is fully supported, and most of its surprises come from that one choice.
This guide covers what multiple inheritance in C++ is for, how to implement several interfaces in one class, how to resolve the same member name in two bases, the diamond problem and how virtual base classes solve it, and the order in which bases are constructed. It builds on inheritance in C++, which covers single inheritance, access control and virtual destructors. Every program was compiled and run for this article on Ubuntu 24.04 with GCC 13.3 and Clang 18.1.3 under -std=c++17 and -std=c++20 with -Wall -Wextra -pedantic, and runs clean under AddressSanitizer and UndefinedBehaviorSanitizer; the examples that must not compile are shown with the errors both compilers gave. All output is captured verbatim, and the code is in a GitHub repository whose build repeats these checks on each commit.
The Short Answer
Does C++ support multiple inheritance? Yes. A class can list several base classes: class Invoice : public Printable, public Serializable. Java and C# allow only one base class plus any number of interfaces; Python allows several base classes.
What is the diamond problem? Two base classes that share a common base of their own. Without virtual inheritance, the most-derived object contains two copies of that common base, and references to it are ambiguous.
How does C++ solve the diamond problem? With virtual inheritance: class Scanner : virtual public Device. Every class that inherits Device virtually shares one Device subobject, which the most-derived class constructs.
What Is Multiple Inheritance in C++?
Multiple inheritance in C++ means a class has more than one direct base class. The derived object contains one subobject for each base, can be used wherever a reference or pointer to any of its public bases is expected, and inherits the members of all of them. Name clashes between bases must be resolved explicitly, and a base reached through two paths is duplicated unless it is inherited virtually.
The syntax is a comma-separated list, each base with its own access specifier:
class Derived : public Base1, public Base2 {};
Every rule from single inheritance still applies to each base separately: access, constructors, hiding and overriding. What is new comes from having two bases at once, and the sections below take those one at a time. How the same feature looks in Java and Python is compared in inheritance in object-oriented programming.
Implementing Several Interfaces
The use of multiple inheritance that rarely causes trouble is the one Java and C# also allow: one class implementing several independent interfaces. In C++, an interface is an abstract class with only pure virtual functions and a virtual destructor.
// interfaces.cpp - one class implementing two independent interfaces.
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
class Printable {
public:
virtual ~Printable() = default;
virtual std::string render() const = 0;
};
class Serializable {
public:
virtual ~Serializable() = default;
virtual std::string to_json() const = 0;
};
class Invoice : public Printable, public Serializable {
public:
Invoice(std::string id, double total) : id_(std::move(id)), total_(total) {}
std::string render() const override { return "Invoice " + id_ + ": " + money(); }
std::string to_json() const override {
return "{\"id\":\"" + id_ + "\",\"total\":" + money() + "}";
}
private:
std::string money() const {
std::ostringstream out;
out << std::fixed << std::setprecision(2) << total_;
return out.str();
}
std::string id_;
double total_;
};
void print(const Printable& p) { std::cout << "print: " << p.render() << '\n'; }
void save(const Serializable& s) { std::cout << "save: " << s.to_json() << '\n'; }
int main() {
Invoice inv("A-17", 249.5);
print(inv);
save(inv);
// The two base subobjects live at different addresses inside one object.
auto addr = [](const void* p) { return reinterpret_cast<std::uintptr_t>(p); };
const Printable* as_printable = &inv;
const Serializable* as_serializable = &inv;
std::cout << "Printable part at offset " << addr(as_printable) - addr(&inv) << '\n';
std::cout << "Serializable part at offset " << addr(as_serializable) - addr(&inv) << '\n';
}
Output:
print: Invoice A-17: 249.50
save: {"id":"A-17","total":249.50}
Printable part at offset 0
Serializable part at offset 8
print knows only Printable and save knows only Serializable, and both accepted the same Invoice. Each function depends on the smallest interface it needs, which is the point of splitting them.
The last two lines show what that costs. An Invoice contains a Printable subobject at offset 0 and a Serializable subobject at offset 8 (x86-64, GCC and Clang), each with its own pointer to a virtual table. So converting &inv to const Serializable* produced a different address from &inv: the compiler added 8. That adjustment is automatic for static_cast and implicit conversions, (the listing’s reinterpret_cast to an integer is only for printing the offsets), which is one more reason never to convert between class pointers with reinterpret_cast or a cast through void*. How virtual tables work is covered in the virtual functions guide.
When Two Bases Use the Same Name
If both bases declare a member with the same name, the derived class inherits both, and using the plain name is ambiguous:
// ambiguous_member.cpp - must not compile: power() is declared in both bases,
// so the unqualified name is ambiguous.
struct Engine { int power() const { return 150; } };
struct Battery { int power() const { return 60; } };
struct Hybrid : Engine, Battery {};
int main() {
Hybrid car;
return car.power();
}
GCC first, then Clang:
tests/compile-fail/ambiguous_member.cpp:10:16: error: request for member 'power' is ambiguous
tests/compile-fail/ambiguous_member.cpp:10:16: error: member 'power' found in multiple base classes of different types
The two power() functions have nothing to do with each other: one is an engine’s output in kilowatts, the other a battery’s capacity in kilowatt-hours. The fix is to decide in the derived class what each name means:
// name_clash.cpp - two bases with a member of the same name.
#include <iostream>
struct Engine {
int power() const { return 150; } // kilowatts
void start() const { std::cout << "engine started\n"; }
};
struct Battery {
int power() const { return 60; } // kilowatt-hours
void charge() const { std::cout << "battery charging\n"; }
};
struct Hybrid : Engine, Battery {
// Pick one meaning for the unqualified name,
using Engine::power;
// and give the other a name of its own.
int capacity() const { return Battery::power(); }
};
int main() {
Hybrid car;
car.start();
car.charge();
std::cout << "power: " << car.power() << " kW\n";
std::cout << "capacity: " << car.capacity() << " kWh\n";
std::cout << "qualified: " << car.Engine::power() << " and " << car.Battery::power() << '\n';
}
Output:
engine started
battery charging
power: 150 kW
capacity: 60 kWh
qualified: 150 and 60
using Engine::power; makes the unqualified name refer to the engine’s version, and capacity() gives the battery’s version a name that says what it is. The qualified forms car.Engine::power() and car.Battery::power() always work. Members with different names, start() and charge(), need no help at all. Overload resolution does not settle a clash like this: name lookup fails before overloads are considered, even when the parameter lists differ.
The Diamond Problem
A diamond appears when two base classes share a base of their own. A copier is both a scanner and a printer, and both are devices:
// diamond.cpp - the diamond without virtual inheritance: a Copier contains
// two separate Device objects.
#include <iostream>
struct Device {
int id;
explicit Device(int i) : id(i) { std::cout << " Device(" << i << ") constructed\n"; }
};
struct Scanner : Device {
Scanner() : Device(1) {}
};
struct Printer : Device {
Printer() : Device(2) {}
};
struct Copier : Scanner, Printer {};
int main() {
std::cout << "building a Copier:\n";
Copier c;
std::cout << "Scanner's Device id: " << c.Scanner::id << '\n';
std::cout << "Printer's Device id: " << c.Printer::id << '\n';
std::cout << "sizeof(Device) = " << sizeof(Device) << ", sizeof(Copier) = " << sizeof(Copier) << '\n';
}
Output:
building a Copier:
Device(1) constructed
Device(2) constructed
Scanner's Device id: 1
Printer's Device id: 2
sizeof(Device) = 4, sizeof(Copier) = 8
Device‘s constructor ran twice, and the Copier holds two Device objects with different ids: 8 bytes for two 4-byte Device subobjects. Sometimes that is correct (two independent devices that happen to share a type), but usually it is not, and it makes the plain name id and the conversion to Device* ambiguous:
// ambiguous_base.cpp - must not compile: without virtual inheritance a Copier
// contains two Device objects, so Copier* cannot convert to Device*.
struct Device { int id = 0; };
struct Scanner : Device {};
struct Printer : Device {};
struct Copier : Scanner, Printer {};
int main() {
Copier c;
Device* d = &c;
return d->id;
}
GCC first, then Clang (Clang lists both paths):
tests/compile-fail/ambiguous_base.cpp:10:17: error: 'Device' is an ambiguous base of 'Copier'
tests/compile-fail/ambiguous_base.cpp:10:17: error: ambiguous conversion from derived class 'Copier' to base class 'Device':
struct Copier -> Scanner -> Device
struct Copier -> Printer -> Device
Virtual Inheritance: One Shared Base
Declaring the shared base virtual in every class that inherits it gives the most-derived object exactly one copy:
// virtual_base.cpp - the diamond with virtual inheritance: one shared Device,
// constructed by the most-derived class.
#include <iostream>
struct Device {
int id;
explicit Device(int i) : id(i) { std::cout << " Device(" << i << ") constructed\n"; }
};
struct Scanner : virtual Device {
Scanner() : Device(1) {} // ignored when Scanner is a base of Copier
};
struct Printer : virtual Device {
Printer() : Device(2) {} // ignored as well
};
struct Copier : Scanner, Printer {
Copier() : Device(3) {} // the most-derived class constructs Device
};
int main() {
std::cout << "building a Copier:\n";
Copier c;
std::cout << "one Device, id " << c.id << '\n';
std::cout << "same object: " << std::boolalpha
<< (static_cast<Device*>(static_cast<Scanner*>(&c)) ==
static_cast<Device*>(static_cast<Printer*>(&c))) << '\n';
std::cout << "sizeof(Copier) = " << sizeof(Copier) << '\n';
std::cout << "building a Scanner on its own:\n";
Scanner s;
std::cout << "its Device id: " << s.id << '\n';
}
Output:
building a Copier:
Device(3) constructed
one Device, id 3
same object: true
sizeof(Copier) = 24
building a Scanner on its own:
Device(1) constructed
its Device id: 1
Four things changed:
- One
Device. Converting throughScannerand throughPrinterreached the same object (same object: true), and the plain namec.idis no longer ambiguous. - The most-derived class constructs it.
Copier‘sDevice(3)ran; theDevice(1)andDevice(2)initializers inScannerandPrinterwere skipped ([class.base.init]/7). AScannerbuilt on its own still used itsDevice(1). This rule exists because only the most-derived class knows that the base is shared, and it means every class at the bottom of a virtual hierarchy must initialize the virtual base, or it gets the default constructor; hereDevicehas none, so leavingDevice(3)out would not compile. - Size and indirection.
sizeof(Copier)went from 8 to 24 bytes. The sharedDevicesits at an offset that depends on the most-derived type, so each path to it carries a pointer (here, the vptr at offsets 0 and 8) used to find it at run time. - The virtual base is constructed first, before any non-virtual base ([class.base.init]/15). The program cannot show this, because
ScannerandPrinterprint nothing;base_order.cppbelow shows how declaration order works for the other bases.
Virtual inheritance is a decision made by the intermediate classes (Scanner and Printer), not by the class at the bottom. That is why it belongs in hierarchies designed for it, such as the standard library’s I/O streams, where std::iostream derives from std::istream and std::ostream, which share a virtual base std::basic_ios.
The isocpp.org FAQ on multiple and virtual inheritance covers more cases, including delegating to a sister class.
Base Class Construction Order
Bases are constructed in the order they are listed in the class declaration, not the order of the constructor’s initializer list:
// base_order.cpp - bases are constructed in declaration order, whatever order
// the constructor's initializer list uses.
#include <iostream>
struct Logger { Logger() { std::cout << " Logger\n"; } };
struct Network { Network() { std::cout << " Network\n"; } };
struct Storage { Storage() { std::cout << " Storage\n"; } };
struct Service : Storage, Logger, Network {
Service() : Network(), Logger(), Storage() { std::cout << " Service body\n"; }
};
int main() {
std::cout << "construction order:\n";
Service s;
}
Output:
construction order:
Storage
Logger
Network
Service body
The initializer list said Network(), Logger(), Storage(), and the bases were built Storage, Logger, Network, the declaration order. Destruction runs in reverse. Both compilers warn about the mismatch under -Wall (GCC first, then Clang):
src/base_order.cpp:10:46: warning: base 'Network' will be initialized after [-Wreorder]
src/base_order.cpp:10:46: warning: base 'Logger' [-Wreorder]
src/base_order.cpp:10:5: warning: when initialized here [-Wreorder]
src/base_order.cpp:10:17: warning: initializer order does not match the declaration order [-Wreorder-ctor]
The order matters when one base’s constructor depends on another having been built: a Network that logs through Logger is safe only if Logger is listed before it in the base list. Write the initializer list in declaration order and the warning stays quiet and the code says what it does. The complete order, with virtual bases first, is: virtual bases (depth-first, left to right), then direct non-virtual bases in declaration order, then members, then the constructor body. The single-inheritance order is shown in inheritance in C++.
Multiple Inheritance in C++, Java and Python
| Aspect | C++ | Java | Python |
|---|---|---|---|
| Several base classes | Yes | No; one class plus interfaces | Yes |
| Interfaces | Abstract classes with pure virtual functions | interface | Abstract base classes (abc) or protocols |
| Shared base in a diamond | Duplicated unless inherited virtual | Not possible for classes | Always one, ordered by the MRO |
| Same name in two bases | Ambiguous until qualified or using | Default methods must be overridden | First match in the MRO wins |
| Who constructs the shared base | The most-derived class | Not applicable | Each __init__ calls super().__init__() cooperatively |
The difference that matters most in practice is the third row. Python never duplicates a shared base and resolves names by a fixed method resolution order; C++ duplicates it unless you ask otherwise, and makes you resolve name clashes yourself.
When to Use Multiple Inheritance
| Situation | Recommendation |
|---|---|
| A class implementing several independent interfaces | Use it: abstract bases with no data rarely cause problems |
| Combining small policy or mixin classes with no shared base | Reasonable; keep them stateless or independent |
| Two bases that share a base, by design | Use virtual inheritance in the intermediate classes, and initialize the virtual base in each most-derived class |
| Reusing implementation from two concrete classes | Prefer composition: two member objects |
| A diamond you did not plan for | Restructure; virtual inheritance cannot be added from the bottom |
Composition is covered with an example in inheritance in C++, and the general case for preferring it in object-oriented programming in C++.
Key Takeaways
- C++ allows several base classes, each with its own access specifier; write
publicfor every one. - Interfaces are the safe case. An
InvoiceimplementedPrintableandSerializable, and each base part sat at its own offset, which the compiler adjusts for automatically. - A name in two bases is ambiguous until you resolve it with qualification or a using-declaration.
- A non-virtual diamond duplicates the shared base.
Devicewas constructed twice and the conversion toDevice*did not compile. - Virtual inheritance shares it, and the most-derived class constructs it:
Copier‘sDevice(3)ran and the intermediate initializers were skipped. - Shared bases cost space and an indirection: the
Copiergrew from 8 to 24 bytes. - Bases are built in declaration order; both compilers warn when the initializer list disagrees.
Frequently Asked Questions
Conclusion
Multiple inheritance is not dangerous so much as explicit. C++ does not pick a winner when two bases use the same name, does not merge a shared base unless asked, and does not let an intermediate class construct a base it shares. Each of those decisions is yours, which is why the interface case, with no data and no shared state, is the one that stays simple.
The C++ programming tutorials are collected in the C++ section.
Source Code and Tests
All programs on this page are in the oop/multiple-inheritance directory of the MYCPLUS C++ examples repository.
Build and test:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
bash tests/run_tests.sh g++ c++17
bash tests/sanitizers.sh g++
What the build checks. On Linux, with GCC and Clang under C++17 and C++20, it builds every program with -Wall -Wextra -pedantic -Werror, runs it and compares its output with this page; checks that base_order.cpp still produces the -Wreorder warning quoted here; and checks that both compile-fail examples are rejected with the expected error. A sanitizer job confirms that every program runs clean under AddressSanitizer and UndefinedBehaviorSanitizer. A macOS job runs the same tests with Apple Clang, and a Windows job builds every program with MSVC at /W4 /WX and compares the output, printing virtual_base without comparing it because MSVC lays out virtual bases differently. The base-subobject offsets of the virtual diamond in the figure were measured locally with a separate program and are not part of the build.


