Construct a Copier from a Scanner and a Printer that each derive from Gadget, and the Copier comprises two Gadget objects: one with id 1, one with id 2. Add digital to the 2 base declarations and it comprises one, with id 3, constructed by a constructor that Scanner and Printer by no means get to run, and the item grows from 8 bytes to 24. A number of inheritance in C++ is absolutely supported, and most of its surprises come from that one alternative.
This information covers what a number of inheritance in C++ is for, implement a number of interfaces in a single class, resolve the identical member identify in two bases, the diamond drawback and the way digital base courses remedy it, and the order during which bases are constructed. It builds on inheritance in C++, which covers single inheritance, entry management and digital destructors. Each program was compiled and run for this text on Ubuntu 24.04 with GCC 13.3 and Clang 18.1.3 below -std=c++17 and -std=c++20 with -Wall -Wextra -pedantic, and runs clear below AddressSanitizer and UndefinedBehaviorSanitizer; the examples that should not compile are proven with the errors each compilers gave. All output is captured verbatim, and the code is in a GitHub repository whose construct repeats these checks on every commit.
The Brief Reply
Does C++ assist a number of inheritance? Sure. A category can checklist a number of base courses: class Bill : public Printable, public Serializable. Java and C# enable just one base class plus any variety of interfaces; Python permits a number of base courses.
What’s the diamond drawback? Two base courses that share a typical base of their very own. With out digital inheritance, the most-derived object comprises two copies of that frequent base, and references to it are ambiguous.
How does C++ remedy the diamond drawback? With digital inheritance: class Scanner : digital public Gadget. Each class that inherits Gadget nearly shares one Gadget subobject, which the most-derived class constructs.
What Is A number of Inheritance in C++?
A number of inheritance in C++ means a category has a couple of direct base class. The derived object comprises one subobject for every base, can be utilized wherever a reference or pointer to any of its public bases is predicted, and inherits the members of all of them. Identify clashes between bases have to be resolved explicitly, and a base reached by way of two paths is duplicated except it’s inherited nearly.
The syntax is a comma-separated checklist, every base with its personal entry specifier:
class Derived : public Base1, public Base2 {};
Each rule from single inheritance nonetheless applies to every base individually: entry, constructors, hiding and overriding. What’s new comes from having two bases directly, and the sections under take these separately. How the identical characteristic appears to be like in Java and Python is in contrast in inheritance in object-oriented programming.
Implementing A number of Interfaces
Using a number of inheritance that hardly ever causes bother is the one Java and C# additionally enable: one class implementing a number of unbiased interfaces. In C++, an interface is an summary class with solely pure digital features and a digital destructor.
// interfaces.cpp - one class implementing two unbiased interfaces.
#embody <cstdint>
#embody <iomanip>
#embody <iostream>
#embody <sstream>
#embody <string>
#embody <utility>
class Printable {
public:
digital ~Printable() = default;
digital std::string render() const = 0;
};
class Serializable {
public:
digital ~Serializable() = default;
digital std::string to_json() const = 0;
};
class Bill : public Printable, public Serializable {
public:
Bill(std::string id, double complete) : id_(std::transfer(id)), total_(complete) {}
std::string render() const override { return "Bill " + id_ + ": " + cash(); }
std::string to_json() const override {
return "{"id":"" + id_ + "","complete":" + cash() + "}";
}
non-public:
std::string cash() const {
std::ostringstream out;
out << std::fastened << 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 primary() {
Bill inv("A-17", 249.5);
print(inv);
save(inv);
// The 2 base subobjects dwell at totally 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 half at offset " << addr(as_printable) - addr(&inv) << 'n';
std::cout << "Serializable half at offset " << addr(as_serializable) - addr(&inv) << 'n';
}
Output:
print: Bill A-17: 249.50
save: {"id":"A-17","complete":249.50}
Printable half at offset 0
Serializable half at offset 8
print is aware of solely Printable and save is aware of solely Serializable, and each accepted the identical Bill. Every operate is determined by the smallest interface it wants, which is the purpose of splitting them.
The final two strains present what that prices. An Bill comprises a Printable subobject at offset 0 and a Serializable subobject at offset 8 (x86-64, GCC and Clang), every with its personal pointer to a digital desk. So changing &inv to const Serializable* produced a special handle from &inv: the compiler added 8. That adjustment is computerized for static_cast and implicit conversions, (the itemizing’s reinterpret_cast to an integer is just for printing the offsets), which is another reason by no means to transform between class pointers with reinterpret_cast or a solid by way of void*. How digital tables work is roofed within the digital features information.
When Two Bases Use the Identical Identify
If each bases declare a member with the identical identify, the derived class inherits each, and utilizing the plain identify is ambiguous:
// ambiguous_member.cpp - should not compile: energy() is said in each bases,
// so the unqualified identify is ambiguous.
struct Engine { int energy() const { return 150; } };
struct Battery { int energy() const { return 60; } };
struct Hybrid : Engine, Battery {};
int primary() {
Hybrid automotive;
return automotive.energy();
}
GCC first, then Clang:
checks/compile-fail/ambiguous_member.cpp:10:16: error: request for member 'energy' is ambiguous
checks/compile-fail/ambiguous_member.cpp:10:16: error: member 'energy' present in a number of base courses of various sorts
The 2 energy() features don’t have anything to do with one another: one is an engine’s output in kilowatts, the opposite a battery’s capability in kilowatt-hours. The repair is to resolve within the derived class what every identify means:
// name_clash.cpp - two bases with a member of the identical identify.
#embody <iostream>
struct Engine {
int energy() const { return 150; } // kilowatts
void begin() const { std::cout << "engine startedn"; }
};
struct Battery {
int energy() const { return 60; } // kilowatt-hours
void cost() const { std::cout << "battery chargingn"; }
};
struct Hybrid : Engine, Battery {
// Choose one which means for the unqualified identify,
utilizing Engine::energy;
// and provides the opposite a reputation of its personal.
int capability() const { return Battery::energy(); }
};
int primary() {
Hybrid automotive;
automotive.begin();
automotive.cost();
std::cout << "energy: " << automotive.energy() << " kWn";
std::cout << "capability: " << automotive.capability() << " kWhn";
std::cout << "certified: " << automotive.Engine::energy() << " and " << automotive.Battery::energy() << 'n';
}
Output:
engine began
battery charging
energy: 150 kW
capability: 60 kWh
certified: 150 and 60
utilizing Engine::energy; makes the unqualified identify seek advice from the engine’s model, and capability() provides the battery’s model a reputation that claims what it’s. The certified types automotive.Engine::energy() and automotive.Battery::energy() at all times work. Members with totally different names, begin() and cost(), want no assist in any respect. Overload decision doesn’t settle a conflict like this: identify lookup fails earlier than overloads are thought of, even when the parameter lists differ.
The Diamond Drawback
A diamond seems when two base courses share a base of their very own. A copier is each a scanner and a printer, and each are units:
// diamond.cpp - the diamond with out digital inheritance: a Copier comprises
// two separate Gadget objects.
#embody <iostream>
struct Gadget {
int id;
specific Gadget(int i) : id(i) { std::cout << " Gadget(" << i << ") constructedn"; }
};
struct Scanner : Gadget {
Scanner() : Gadget(1) {}
};
struct Printer : Gadget {
Printer() : Gadget(2) {}
};
struct Copier : Scanner, Printer {};
int primary() {
std::cout << "constructing a Copier:n";
Copier c;
std::cout << "Scanner's Gadget id: " << c.Scanner::id << 'n';
std::cout << "Printer's Gadget id: " << c.Printer::id << 'n';
std::cout << "sizeof(Gadget) = " << sizeof(Gadget) << ", sizeof(Copier) = " << sizeof(Copier) << 'n';
}
Output:
constructing a Copier:
Gadget(1) constructed
Gadget(2) constructed
Scanner's Gadget id: 1
Printer's Gadget id: 2
sizeof(Gadget) = 4, sizeof(Copier) = 8
Gadget‘s constructor ran twice, and the Copier holds two Gadget objects with totally different ids: 8 bytes for 2 4-byte Gadget subobjects. Generally that’s appropriate (two unbiased units that occur to share a sort), however often it’s not, and it makes the plain identify id and the conversion to Gadget* ambiguous:
// ambiguous_base.cpp - should not compile: with out digital inheritance a Copier
// comprises two Gadget objects, so Copier* can't convert to Gadget*.
struct Gadget { int id = 0; };
struct Scanner : Gadget {};
struct Printer : Gadget {};
struct Copier : Scanner, Printer {};
int primary() {
Copier c;
Gadget* d = &c;
return d->id;
}
GCC first, then Clang (Clang lists each paths):
checks/compile-fail/ambiguous_base.cpp:10:17: error: 'Gadget' is an ambiguous base of 'Copier'
checks/compile-fail/ambiguous_base.cpp:10:17: error: ambiguous conversion from derived class 'Copier' to base class 'Gadget':
struct Copier -> Scanner -> Gadget
struct Copier -> Printer -> Gadget
Digital Inheritance: One Shared Base
Declaring the shared base digital in each class that inherits it provides the most-derived object precisely one copy:
// virtual_base.cpp - the diamond with digital inheritance: one shared Gadget,
// constructed by the most-derived class.
#embody <iostream>
struct Gadget {
int id;
specific Gadget(int i) : id(i) { std::cout << " Gadget(" << i << ") constructedn"; }
};
struct Scanner : digital Gadget {
Scanner() : Gadget(1) {} // ignored when Scanner is a base of Copier
};
struct Printer : digital Gadget {
Printer() : Gadget(2) {} // ignored as properly
};
struct Copier : Scanner, Printer {
Copier() : Gadget(3) {} // the most-derived class constructs Gadget
};
int primary() {
std::cout << "constructing a Copier:n";
Copier c;
std::cout << "one Gadget, id " << c.id << 'n';
std::cout << "identical object: " << std::boolalpha
<< (static_cast<Gadget*>(static_cast<Scanner*>(&c)) ==
static_cast<Gadget*>(static_cast<Printer*>(&c))) << 'n';
std::cout << "sizeof(Copier) = " << sizeof(Copier) << 'n';
std::cout << "constructing a Scanner by itself:n";
Scanner s;
std::cout << "its Gadget id: " << s.id << 'n';
}
Output:
constructing a Copier:
Gadget(3) constructed
one Gadget, id 3
identical object: true
sizeof(Copier) = 24
constructing a Scanner by itself:
Gadget(1) constructed
its Gadget id: 1
4 issues modified:
- One
Gadget. Changing by way ofScannerand thruPrinterreached the identical object (identical object: true), and the plain identifyc.idis not ambiguous. - Essentially the most-derived class constructs it.
Copier‘sGadget(3)ran; theGadget(1)andGadget(2)initializers inScannerandPrinterhave been skipped ([class.base.init]/7). AScannerconstructed by itself nonetheless used itsGadget(1). This rule exists as a result of solely the most-derived class is aware of that the bottom is shared, and it means each class on the backside of a digital hierarchy should initialize the digital base, or it will get the default constructor; right hereGadgethas none, so leavingGadget(3)out wouldn’t compile. - Measurement and indirection.
sizeof(Copier)went from 8 to 24 bytes. The sharedGadgetsits at an offset that is determined by the most-derived kind, so every path to it carries a pointer (right here, the vptr at offsets 0 and eight) used to search out it at run time. - The digital base is constructed first, earlier than any non-virtual base ([class.base.init]/15). This system can’t present this, as a result of
ScannerandPrinterprint nothing;base_order.cppunder exhibits how declaration order works for the opposite bases.
Digital inheritance is a call made by the intermediate courses (Scanner and Printer), not by the category on the backside. That’s the reason it belongs in hierarchies designed for it, similar to the usual library’s I/O streams, the place std::iostream derives from std::istream and std::ostream, which share a digital base std::basic_ios.
The isocpp.org FAQ on a number of and digital inheritance covers extra instances, together with delegating to a sister class.
Base Class Development Order
Bases are constructed within the order they’re listed within the class declaration, not the order of the constructor’s initializer checklist:
// base_order.cpp - bases are constructed in declaration order, no matter order
// the constructor's initializer checklist makes use of.
#embody <iostream>
struct Logger { Logger() { std::cout << " Loggern"; } };
struct Community { Community() { std::cout << " Networkn"; } };
struct Storage { Storage() { std::cout << " Storagen"; } };
struct Service : Storage, Logger, Community {
Service() : Community(), Logger(), Storage() { std::cout << " Service bodyn"; }
};
int primary() {
std::cout << "building order:n";
Service s;
}
Output:
building order:
Storage
Logger
Community
Service physique
The initializer checklist mentioned Community(), Logger(), Storage(), and the bases have been constructed Storage, Logger, Community, the declaration order. Destruction runs in reverse. Each compilers warn concerning the mismatch below -Wall (GCC first, then Clang):
src/base_order.cpp:10:46: warning: base 'Community' will likely be initialized after [-Wreorder]
src/base_order.cpp:10:46: warning: base 'Logger' [-Wreorder]
src/base_order.cpp:10:5: warning: when initialized right here [-Wreorder]
src/base_order.cpp:10:17: warning: initializer order doesn't match the declaration order [-Wreorder-ctor]
The order issues when one base’s constructor is determined by one other having been constructed: a Community that logs by way of Logger is protected provided that Logger is listed earlier than it within the base checklist. Write the initializer checklist in declaration order and the warning stays quiet and the code says what it does. The entire order, with digital bases first, is: digital bases (depth-first, left to proper), then direct non-virtual bases in declaration order, then members, then the constructor physique. The only-inheritance order is proven in inheritance in C++.
A number of Inheritance in C++, Java and Python
| Side | C++ | Java | Python |
|---|---|---|---|
| A number of base courses | Sure | No; one class plus interfaces | Sure |
| Interfaces | Summary courses with pure digital features | interface |
Summary base courses (abc) or protocols |
| Shared base in a diamond | Duplicated except inherited digital |
Not attainable for courses | All the time one, ordered by the MRO |
| Identical identify in two bases | Ambiguous till certified or utilizing |
Default strategies have to be overridden | First match within the MRO wins |
| Who constructs the shared base | Essentially the most-derived class | Not relevant | Every __init__ calls tremendous().__init__() cooperatively |
The distinction that issues most in apply is the third row. Python by no means duplicates a shared base and resolves names by a hard and fast methodology decision order; C++ duplicates it except you ask in any other case, and makes you resolve identify clashes your self.
When to Use A number of Inheritance
| Scenario | Suggestion |
|---|---|
| A category implementing a number of unbiased interfaces | Use it: summary bases with no information hardly ever trigger issues |
| Combining small coverage or mixin courses with no shared base | Affordable; hold them stateless or unbiased |
| Two bases that share a base, by design | Use digital inheritance within the intermediate courses, and initialize the digital base in every most-derived class |
| Reusing implementation from two concrete courses | Desire composition: two member objects |
| A diamond you didn’t plan for | Restructure; digital inheritance can’t be added from the underside |
Composition is roofed with an instance in inheritance in C++, and the overall case for preferring it in object-oriented programming in C++.
Key Takeaways
- C++ permits a number of base courses, every with its personal entry specifier; write
publicfor each one. - Interfaces are the protected case. An
BillappliedPrintableandSerializable, and every base half sat at its personal offset, which the compiler adjusts for robotically. - A reputation in two bases is ambiguous till you resolve it with qualification or a using-declaration.
- A non-virtual diamond duplicates the shared base.
Gadgetwas constructed twice and the conversion toGadget*didn’t compile. - Digital inheritance shares it, and the most-derived class constructs it:
Copier‘sGadget(3)ran and the intermediate initializers have been skipped. - Shared bases price house and an indirection: the
Copiergrew from 8 to 24 bytes. - Bases are inbuilt declaration order; each compilers warn when the initializer checklist disagrees.
Incessantly Requested Questions
Conclusion
A number of inheritance isn’t harmful a lot as specific. C++ doesn’t decide a winner when two bases use the identical identify, doesn’t merge a shared base except requested, and doesn’t let an intermediate class assemble a base it shares. Every of these choices is yours, which is why the interface case, with no information and no shared state, is the one which stays easy.
The C++ programming tutorials are collected within the C++ part.
Supply Code and Checks
All packages on this web page are within the oop/multiple-inheritance listing of the MYCPLUS C++ examples repository.
Construct and check:
cmake -S . -B construct -DCMAKE_BUILD_TYPE=Launch
cmake --build construct
bash checks/run_tests.sh g++ c++17
bash checks/sanitizers.sh g++
What the construct checks. On Linux, with GCC and Clang below C++17 and C++20, it builds each program with -Wall -Wextra -pedantic -Werror, runs it and compares its output with this web page; checks that base_order.cpp nonetheless produces the -Wreorder warning quoted right here; and checks that each compile-fail examples are rejected with the anticipated error. A sanitizer job confirms that each program runs clear below AddressSanitizer and UndefinedBehaviorSanitizer. A macOS job runs the identical checks with Apple Clang, and a Home windows job builds each program with MSVC at /W4 /WX and compares the output, printing virtual_base with out evaluating it as a result of MSVC lays out digital bases in another way. The bottom-subobject offsets of the digital diamond within the determine have been measured domestically with a separate program and are usually not a part of the construct.

