C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
25: Constructor Overloading and Delegation
You've likely already encountered the need to provide multiple ways to initialize an object. Maybe your class needs a "default" state, but it also needs to be able to take specific parameters when the caller already has that data. In C++, we handle this with constructor overloading. On the surface, it seems simple: just write another constructor with a different signature. But as your classes grow, you'll find that overloading often leads to a very specific, very annoying kind of code duplication.
The Copy-Paste Trap
Let's look at a GameCharacter class. Every character needs a name, a health value, and a strength rating. In a naive implementation, you might write a "full" constructor and then a few "convenience" constructors for common scenarios—like a default character or a character with just a name.
class GameCharacter {
std::string name;
int health;
int strength;
public:
// The "Master" constructor
GameCharacter(std::string n, int h, int s) {
name = n;
health = h;
strength = s;
std::cout << "Character " << name << " fully initialized.\n";
}
// Convenience: Default health and strength
GameCharacter(std::string n) {
name = n;
health = 100;
strength = 10;
std::cout << "Character " << name << " initialized with defaults.\n";
}
// Convenience: Totally default
GameCharacter() {
name = "Unknown";
health = 100;
strength = 10;
std::cout << "Default character created.\n";
}
};
At first glance, this works. But look closely at the assignments. I'm repeating the default values (100 and 10) in both the second and third constructors. This is a red flag. If I decide later that the base health should be 150, I have to hunt down every single constructor and update the value manually. It's a tiny mistake to make, but in a production codebase with dozens of fields, it's exactly where bugs hide.
The Maintenance Tax
The problem isn't just the values; it's the logic. Imagine if I added a validation check to ensure health never exceeds 1000. I'd have to paste that if statement into every single constructor. This is what I call the "maintenance tax." You aren't just writing code once; you're signing up to update it in three different places every time the business logic changes.
You might be tempted to move this logic into a private init() method. While that works, it's a bit clunky in C++ because it happens after the members have already been initialized by the compiler. There is a cleaner, more idiomatic way to handle this: Constructor Delegation.
Delegating the Heavy Lifting
Introduced in C++11, constructor delegation allows one constructor to call another constructor from the same class. The trick is that this happens in the initializer list, not in the body of the function. This ensures that the "master" constructor handles all the actual setup, and the convenience constructors simply act as wrappers that pass in default values.
class GameCharacter {
std::string name;
int health;
int strength;
public:
// This is now our single point of truth
GameCharacter(std::string n, int h, int s)
: name(n), health(h), strength(s)
{
std::cout << "Character " << name << " created.\n";
}
// Delegate to the master constructor
GameCharacter(std::string n) : GameCharacter(n, 100, 10) {
// Body can be empty or used for additional specific logic
}
// Delegate to the master constructor
GameCharacter() : GameCharacter("Unknown", 100, 10) {
}
};
I love this approach because it centralizes the initialization logic. If I change the way health is processed, I only change it in one place. The other constructors don't care how the character is built; they just know what values to provide to the builder.
One important rule to remember: you can't have a "circle" of delegation. Constructor A cannot delegate to B if B delegates back to A. The compiler will catch this, but it's worth keeping in mind when you're designing complex inheritance hierarchies. Also, remember that if you delegate, you cannot initialize other members in the initializer list of the delegating constructor. You have to pick one: either you delegate to another constructor, or you initialize members yourself. You can't do both in the same list.
📋 Practical Task
Exercise: Refactoring the NetworkConnection Class
You have been handed a NetworkConnection class that is a maintenance nightmare. It has three constructors that all repeat the same initialization logic for timeout and retryCount. Your task is to refactor this class using Constructor Delegation to ensure there is only one place where the member variables are actually assigned.
Requirements:
- Create a "Master" constructor that takes
ipAddress(string),port(int),timeout(int), andretryCount(int). - Create a second constructor that takes only
ipAddressandport, delegating to the master constructor with a default timeout of30and retry count of3. - Create a third default constructor that delegates to the master constructor with a default IP of
"127.0.0.1", port80, timeout30, and retry count3. - Ensure that no member variables are assigned inside the curly braces
{}of the constructors; use the initializer list for delegation.
// Start with this structure:
class NetworkConnection {
std::string ipAddress;
int port;
int timeout;
int retryCount;
public:
// Implement your constructors here
};There are no comments for now.