Before a derived class's own fields can be initialized, the base class part of the object has to be built first. This lesson covers how derived class constructors call base class constructors, the member initializer list syntax for passing arguments to a base constructor, the strict order of construction and destruction across the hierarchy, the C++11 using Base::Base; shortcut for inheriting constructors verbatim, and the common bugs that arise when these rules are violated.
When a class inherits from another, every instance of the derived class contains a complete instance of the base class as a sub-object. Constructing the derived object means constructing both parts in the right order.
Output:
The base class constructor ran first, then the derived class constructor. By the time DigitalProduct's body executes, name and price already hold valid values, because the Product part of the object has been fully constructed.
This order is fixed by the language. There's no way to construct the derived part before the base part, because the derived part may depend on the base's fields, and overriding a virtual function on a half-constructed object would be unsafe.
A derived class invokes a base class constructor as the first item in its member initializer list. The syntax is BaseClassName(args).
Product(n, p) selects which Product constructor to call and forwards the arguments. downloadUrl(url) then initializes the derived class's own field. The order in the initializer list is conventional (base first, then members), but the compiler does not use that order: it always constructs the base first, then members in declaration order, regardless of how you write the list. The compiler may warn if your initializer list disagrees with the construction order.
If you don't write a base class call in the initializer list, the compiler inserts Base() for you: the default constructor of the base class. This works fine if Base has a default constructor:
Output:
PhysicalProduct(double) doesn't mention Product in its initializer list, so the compiler called Product(), which set name to "Unnamed" and price to 0.0. This works only because Product has a default constructor.
If the base class has no default constructor, you must invoke a base constructor explicitly, or the code fails to compile:
Compiler error (g++):
The fix is to call the right Product constructor:
Quick Check: Will this code compile?
Base() implicitly.Base has no default constructor and Derived doesn't call any other Base constructor.<details> <summary>Answer</summary>
B. Derived(int) does not invoke any Base constructor in its initializer list, so the compiler tries to use Base(). But Base only declares Base(int), so the implicit default is suppressed. The fix is to add Base(v) (or some specific value) to Derived's initializer list.
</details>
When you create an object of a derived class, the compiler runs a precise sequence of steps:
The same order holds however deep the hierarchy goes: top-most ancestor first, then each level down, then members of each level in declaration order.
A three-level hierarchy makes the order visible:
Output:
The chain runs strictly top to bottom: Trackable, then Product, then DigitalProduct. The destructors run in reverse order when the object is destroyed.
The mirror order means each class can rely on its own members and its base class being fully alive in its destructor, since they're still around when the destructor body runs.
Quick Check: What does this print?
<details> <summary>Answer</summary>
Construction goes top-down (A, B, C). Destruction goes bottom-up, the exact reverse.
</details>
When the base needs data to construct itself, the derived class's constructor accepts those arguments and forwards them in the initializer list. This is the common case for any non-trivial hierarchy.
Output:
DigitalProduct's constructor takes five parameters and forwards three of them to Product. The initializer list lists the base first, then each derived member.
A common variation is to pass derived-class-specific defaults to the base constructor:
Here the derived class supplies fixed values for two of the base parameters, exposing a simpler interface to its callers. The base class doesn't need to know that the derived class did this.
Each constructor in the derived class can call a different base constructor. If the base has overloaded constructors, the derived class can pick which one to invoke in each of its own overloads.
Output:
Each derived constructor picks its matching base constructor. The compiler resolves which base constructor to call based on the types of arguments inside the initializer list, exactly the same way it resolves any overloaded call.
using Base::Base;Sometimes the derived class has nothing new to add to the base's constructors. The derived class either uses the base's parameters as-is, or it adds new members that have default values. In that case, writing constructors that only forward to the base is repetitive:
C++11 introduced a shortcut: `using Base::Base;` inside the derived class brings all of the base's constructors into scope as if they were the derived class's own.
Output:
Each DigitalProduct invocation forwards to the matching Product constructor. The derived class's new member downloadUrl is initialized to its in-class default of "", since no inherited constructor knows about it.
The using Base::Base; shortcut has some rules and limits:
| Rule | Detail |
|---|---|
| Inherits constructors visible at that scope | Private base constructors are not inherited |
| Members of the derived class get default-initialized | Any inherited constructor only sees the base's parameters; derived members rely on in-class initializers |
| Does not inherit copy/move constructors | These are excluded; the compiler generates them for the derived class as usual |
| You can still write your own constructors | They coexist with the inherited ones, and overload resolution picks the best match |
using Base::Base; fits when the derived class is mostly a wrapper or a marker type that doesn't carry new mandatory state. If your derived class has its own non-defaultable fields, write explicit constructors that initialize them properly instead of relying on inheritance.
Quick Check: What does this print?
<details> <summary>Answer</summary>
The inherited Customer(const std::string&) constructor is callable as PremiumCustomer("Alice"), which forwards "Alice" to the base. tier is initialized by its in-class initializer to 1.
</details>
A few common mistakes appear in constructor inheritance.
If the base class has no default constructor and the derived class doesn't invoke any explicit base constructor, the code fails to compile.
What is wrong?
Product has no default constructor, so the compiler can't build the Product part of a DigitalProduct.
Fix:
Add a name parameter and forward it to Product.
A derived class cannot list base class members in its own initializer list. Only direct, non-inherited members go there.
What is wrong?
Compiler error (g++):
name belongs to Product, not DigitalProduct. The derived class can't initialize a base member directly; it has to go through the base constructor.
Fix: call Product(n) instead of trying to set name directly:
Writing the base call after a member initializer does not change when the base runs. The compiler always constructs the base first, then the members in declaration order.
What is wrong (a subtle warning, not a compile error)?
This compiles, but g++ emits:
Even though the initializer list lists downloadLimit first, the base Product is constructed first, and downloadLimit is constructed last. If Product's constructor relied on downloadLimit (it can't here, but it could in other designs), the program would read an uninitialized member. The warning protects against that confusion.
Fix: write the initializer list in the actual construction order (base first, then members in declaration order):
When the base constructor runs, the derived part of the object doesn't exist yet. Calling a virtual function inside the base constructor will dispatch to the base's version, not the derived override.
The output is I am a Product, not I am a DigitalProduct. This is intentional. If virtual dispatch worked here, you could call a function on a derived object that hasn't been constructed yet, and access uninitialized fields. The rule prevents that. The fix is to either restructure so the call doesn't happen in the constructor, or pass the information you need as a constructor parameter.
virtual in the Base DestructorThis isn't strictly a constructor bug, but it pairs with the rules of construction order. If a base class has any virtual functions, its destructor should be virtual too. Otherwise, deleting a derived object through a base pointer doesn't call the derived destructor:
This is undefined behavior. The destructor of DigitalProduct never ran, so any resources it owned (such as a buffer for downloadUrl) leak. Mark the base destructor virtual whenever the class is meant to be used polymorphically:
Putting all the rules together, here is a small hierarchy modeling products in an online store. The base class is non-trivially constructed, the derived classes pass their own arguments through, and one derived class uses using Base::Base;.
Output:
Product controls construction of name, price, and stock. DigitalProduct adds its own field and supplies a fixed stock value when it calls the base constructor. GiftCard inherits Product's constructors verbatim, so its callers use the same (name, price, stock) signature.
Q1: Why must the base class constructor run before the derived class constructor?
The derived class object physically contains the base class part as a sub-object. The base's fields have to be initialized to valid values before the derived's constructor body runs, because the derived constructor might read or modify those base fields. Running them in the other order would mean either the base accesses uninitialized derived members (impossible if you call virtual functions correctly) or the derived accesses uninitialized base members. C++ enforces the safe order: top-down construction, bottom-up destruction.
Q2: What happens if the derived class doesn't call any base class constructor in its initializer list?
The compiler implicitly calls the base's default constructor. If the base has a default constructor (either user-defined or compiler-generated), this works fine. If the base only declares constructors that take arguments, the implicit default is suppressed, and the derived class fails to compile until it calls one of the available base constructors explicitly. This is the most common reason for "no matching function for call to Base()" errors in inheritance code.
Q3: What is the construction order in a multi-level inheritance hierarchy?
The top-most ancestor is constructed first, then each level down, ending with the most-derived class. Within each level, the data members of that class are constructed in declaration order before the constructor's body runs. Destruction goes in the exact reverse order: most-derived destructor body first, then its members in reverse declaration order, then each base destructor going up the chain. The reversal ensures every destructor sees a fully-alive object during its execution.
Q4: What does `using Base::Base;` do, and what does it not do?
using Base::Base; (added in C++11) brings all of the base class's constructors into the derived class as inherited constructors, so callers can construct the derived class using any of the base's signatures. The derived class's own data members are default-initialized (or use their in-class initializers) when an inherited constructor runs. It does not inherit the copy or move constructors, and it doesn't help if the derived class has its own non-defaultable members that need explicit values. For lightweight wrapper or marker-type derived classes, it removes a lot of boilerplate.
Q5: Why is calling a virtual function from a base class constructor unsafe, and what does it do?
When the base class constructor runs, the derived class part of the object does not exist yet. C++ resolves virtual calls based on the type currently being constructed, so a virtual call inside the base constructor invokes the base's version, not the derived override. This avoids calling a method on a non-existent derived object. If you wrote code expecting the derived override to run, you'll find the program prints the base behavior instead, often without any compiler warning. The right fix is to pass any data you need as constructor arguments instead of calling virtual methods during construction.
Exercise 1: Write a Customer class with a constructor that takes name and email. Write a PremiumCustomer derived class with an additional loyaltyPoints field. Construct a PremiumCustomer named "Alice Tan" with email "alice@example.com" and 240 loyalty points, and print all three values.
Expected Output:
<details> <summary>Solution</summary>
</details>
Exercise 2: What does this program print? Trace the order in which constructors and destructors run.
Expected Output:
<details> <summary>Solution</summary>
Construction goes top-down: A, then B, then C. After main's body finishes, c goes out of scope and destruction runs in reverse: ~C, ~B, ~A.
</details>
Exercise 3: Fix the compile error in this code.
<details> <summary>Solution</summary>
Product has no default constructor, so DigitalProduct must call Product(...) explicitly:
</details>
Exercise 4: Write an Order class that takes orderId and total. Write a ShippedOrder derived class with an additional trackingNumber. Use using Order::Order; so that ShippedOrder also has a two-argument constructor. Then add an explicit three-argument constructor for the case where the tracking number is known up front.
<details> <summary>Solution</summary>
</details>
Exercise 5: Predict the output. Then explain why it isn't what you might naively expect.
Expected Output:
<details> <summary>Solution</summary>
Product's constructor runs while only the Product part of the object exists. C++ deliberately resolves virtual calls based on the currently-constructed type, so print() dispatches to Product::print, not Book::print. This rule prevents calls to derived methods on a not-yet-constructed object. If you wanted Book's behavior, you'd need to either pass the relevant data as constructor arguments or call the method after construction is complete.
</details>
Exercise 6: Fix the bug in this code. The Product destructor should run when a DigitalProduct is deleted through a Product*.
Expected Output (after fix):
<details> <summary>Solution</summary>
The base destructor is not virtual, so delete p; only runs ~Product. To fix it, mark the base destructor virtual:
Now delete p; dispatches through the vtable, runs ~DigitalProduct first, then ~Product, in the correct order.
</details>
Exercise 7: A Vehicle class has a constructor Vehicle(const std::string& make, int year). Write a derived class Truck that adds a double cargoCapacity field. Provide three constructors for Truck: a default constructor, a constructor that takes only the make and uses 0 for cargo capacity, and a full constructor that takes all three values. Use delegation among them to avoid repetition.
<details> <summary>Solution</summary>
The two-arg and zero-arg constructors delegate to the full constructor, which is the only place the base constructor is called.
</details>
Exercise 8: Fix the reorder warning in this code.
<details> <summary>Solution</summary>
The compiler warns because the actual construction order is Product, then url (the first declared member), then downloads, but the initializer list lists them in the opposite order. Reorder the initializer list to match:
The behavior was correct either way (the compiler enforces the real order), but matching the initializer list to the construction order removes the warning and prevents confusion.
</details>
10 quizzes