C++: Advanced Constructors
Last updated: 2026-08-26
In Lesson 28, we learned the basics of constructors.
But in real-world scenarios, you may need to copy objects, move objects, use initializer lists for efficiency...
In this lesson, we'll learn advanced constructor usage.
1. Default Constructor
(1) 1.1 What Is a Default Constructor?
A default constructor is a constructor that takes no parameters.
#include <iostream>
#include <string>
class Student {
private:
std::string name;
int age;
public:
// Default constructor (no parameters)
Student() {
name = "Unknown";
age = 0;
std::cout << "Default constructor called" << std::endl;
}
};
int main() {
Student s1; // ✅ Calls the default constructor
return 0;
}
💡 Key Point: If you don't write any constructor, the compiler automatically generates a default constructor (which does nothing). But if you write any constructor, the compiler will no longer automatically generate a default constructor.
(2) 1.2 Problem: After Writing a Parameterized Constructor, Default Construction Fails
#include <iostream>
#include <string>
class Student {
private:
std::string name;
int age;
public:
// Only wrote a parameterized constructor
Student(const std::string& n, int a) {
name = n;
age = a;
}
};
int main() {
Student s1("Alice", 20); // ✅ OK
Student s2; // ❌ Error: no default constructor
return 0;
}
Fix: Explicitly write a default constructor, or use = default (C++11).
// Method 1: Write it explicitly
Student() {
name = "Unknown";
age = 0;
}
// Method 2: Use = default (C++11, recommended)
Student() = default;
2. Initializer List
(1) 2.1 What Is an Initializer List?
An initializer list initializes members directly after the constructor's colon, rather than assigning values in the function body.
Traditional approach (assignment):
Student(const std::string& n, int a) {
name = n; // This is assignment, not initialization
age = a;
}
Initializer list approach (recommended):
Student(const std::string& n, int a) : name(n), age(a) {
// Function body can be empty
}
💡 Advantages:
- More efficient (direct initialization, not default construction followed by assignment)
- Cases where initializer list is required:
constmembers (must be initialized at creation)- Reference members (must be initialized)
- Member classes without a default constructor
(2) 2.2 Cases Where Initializer List Is Required
Case 1: const members
#include <iostream>
#include <string>
class Student {
private:
const std::string id; // const member
std::string name;
public:
// ❌ Error: const members cannot be assigned in the function body
// Student(const std::string& i, const std::string& n) {
// id = i; // ❌ const member cannot be assigned
// name = n;
// }
// ✅ Correct: use initializer list
Student(const std::string& i, const std::string& n) : id(i), name(n) {
}
};
int main() {
Student s1("20240001", "Alice");
return 0;
}
3. Copy Constructor
(1) 3.1 What Is a Copy Constructor?
A copy constructor is used to initialize one object from another object.
When it's triggered:
- Initializing one object from another
- Passing an object by value to a function (copies)
- Returning an object from a function (may copy)
▶ Example 1: Default Copy Constructor (Difficulty ⭐)
#include <iostream>
#include <string>
class Student {
public:
std::string name;
int age;
// The compiler automatically generates a copy constructor (member-by-member copy)
};
int main() {
Student s1;
s1.name = "Alice";
s1.age = 20;
Student s2 = s1; // ✅ Calls the copy constructor
std::cout << "s2 Name: " << s2.name << std::endl; // Alice
std::cout << "s2 Age: " << s2.age << std::endl; // 20
return 0;
}
Output:
s2 Name: Alice
s2 Age: 20
💡 Key Point: If you don't write a copy constructor, the compiler automatically generates one (member-by-member copy).
(2) 3.2 Custom Copy Constructor
#include <iostream>
#include <string>
class Student {
private:
std::string name;
int age;
public:
// Regular constructor
Student(const std::string& n, int a) : name(n), age(a) {
std::cout << "Regular constructor called:" << name << std::endl;
}
// Copy constructor
Student(const Student& other) : name(other.name), age(other.age) {
std::cout << "Copy constructor called: " << name << std::endl;
}
};
int main() {
Student s1("Alice", 20);
Student s2 = s1; // Calls the copy constructor
return 0;
}
4. Shallow Copy vs Deep Copy
(1) 4.1 The Problem: Default Copying Is a "Shallow Copy"
If a class has pointer members (pointing to heap memory), the default copy constructor only copies the pointer value (the address), not the content the pointer points to.
#include <iostream>
#include <cstring>
class MyClass {
public:
char* data;
MyClass() {
data = new char[5];
std::strcpy(data, "Hello");
}
// ❌ No custom copy constructor (will use default shallow copy)
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1;
MyClass obj2 = obj1; // Shallow copy: obj1.data and obj2.data point to the same memory
// When main ends, obj2 is destructed first (freeing data)
// Then obj1 is destructed (freeing data again, but it's already been freed) → Crash!
return 0;
}
💡 This is the problem with "shallow copy" — two objects' pointers point to the same memory, causing a double free on destruction.
(2) 4.2 Solution: Custom Deep Copy
#include <iostream>
#include <cstring>
class MyClass {
public:
char* data;
MyClass() {
data = new char[5];
std::strcpy(data, "Hello");
}
// Custom copy constructor (deep copy)
MyClass(const MyClass& other) {
data = new char[std::strlen(other.data) + 1];
std::strcpy(data, other.data);
}
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1;
MyClass obj2 = obj1; // Deep copy: obj2.data points to newly allocated memory
std::cout << "obj1.data = " << obj1.data << std::endl;
std::cout << "obj2.data = " << obj2.data << std::endl;
return 0;
}
💡 Key Point: If a class has pointer members, you must define a custom copy constructor (to implement deep copy).
5. Move Constructor (C++11, Optional)
(1) 5.1 What Is a Move Constructor?
A move constructor is a feature introduced in C++11 — it "steals" resources from another object instead of copying them.
Scenario: If you have a temporary object (about to be destroyed), copying its content is wasteful — it's better to just "steal" its resources.
#include <iostream>
#include <string>
class MyClass {
private:
std::string* data;
public:
// Regular constructor
MyClass(const std::string& s) {
data = new std::string(s);
std::cout << "Regular constructor" << std::endl;
}
// Move constructor (C++11)
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // Set other's pointer to null (prevent deallocation on destruction)
std::cout << "Move constructionfunction" << std::endl;
}
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1("Hello");
MyClass obj2 = std::move(obj1); // Calls the move constructor
return 0;
}
💡 Tip: Move constructors are an advanced topic — beginners can get familiar with the concept first and dive deeper later.
6. Practice: Complete Student Class
▶ Example 2: Student Class with Copy Constructor (Difficulty ⭐⭐)
#include <iostream>
#include <string>
class Student {
private:
std::string name;
int age;
public:
// Regular constructor
Student(const std::string& n, int a) : name(n), age(a) {
std::cout << "Regular Constructor: " << name << std::endl;
}
// Copy constructor
Student(const Student& other) : name(other.name), age(other.age) {
std::cout << "Regular Constructor: " << name << std::endl;
}
// Destructor
~Student() {
std::cout << "Destructor: " << name << std::endl;
}
void introduce() {
std::cout << "My name is " << name << ",this year " << age << " years old。" << std::endl;
}
};
int main() {
Student s1("Alice", 20);
Student s2 = s1; // Calls the copy constructor
s1.introduce();
s2.introduce();
return 0;
}
Output:
Regular Constructor: Alice
Regular Constructor: Alice
Destructor: Alice
Destructor: Alice
My name is Alice,this year 20 years old。
My name is Alice,this year 20 years old。
❓ FAQ
Q: When do I need to define a custom copy constructor? A: When the class has pointer members (pointing to heap memory).
If the class only has basic types (
int,double) or STL types (std::string,std::vector), no custom copy constructor is needed — the compiler-generated copy constructor works correctly (STL types implement deep copy themselves).
Q: How much efficiency difference is there between initializer lists and function body assignment? A: For basic types, not much. But for STL types (
std::string,std::vector), a lot —
- Function body assignment: first default-constructs an empty object, then assigns (may allocate memory and copy content)
- Initializer list: directly initializes (only one memory allocation)
Recommendation: Always use initializer lists.
Q: What's the use of move constructors? A: To improve the efficiency of copying temporary objects.
For example, when a function returns a large object, the move constructor can avoid deep copy (by directly "stealing" the temporary object's memory).
▶ Example 3: Delegating Constructor (Difficulty ⭐)
#include <iostream>
#include <string>
class Student {
std::string name;
int age;
public:
Student(std::string n, int a) : name(n), age(a) {}
Student(std::string n) : Student(n, 18) {} // Delegating constructor
Student() : Student("Unknown") {}
void print() { std::cout << name << "," << age << "years old" << std::endl; }
};
int main() {
Student s1("Zhang San", 20);
Student s2("Li Si");
Student s3;
s1.print();
s2.print();
s3.print();
return 0;
}
Output:
Zhang San, 20 years old
Li Si, 18 years old
Unknown, 18 years old
- Default constructors are constructors with no parameters
- Initializer lists are more efficient and can handle
constmembers and reference members - Copy constructors are used to initialize one object from another
- If a class has pointer members, you must define a custom deep copy
- Move constructors (C++11) can improve the efficiency of copying temporary objects
📖 Summary
- Delegating constructors: one constructor calls another constructor
- Inheriting constructors: derived classes inherit base class constructors
- Move construction: transfers resource ownership, avoids deep copy
- explicit: prevents implicit conversion
📝 Exercises
-
Basic (Difficulty ⭐): Define a
Bookclass with two members: title and author. Write the constructor using an initializer list. -
Intermediate (Difficulty ⭐⭐): Define a custom copy constructor for the
Bookclass that implements deep copy (assume the title is stored aschar*, requiring new memory allocation). -
Challenge (Difficulty ⭐⭐⭐): Define a
DynamicArrayclass (dynamic array) with: -
int* data(pointing to heap memory) -
int size(array size)
Implement: 3. Regular constructor (allocates memory) 4. Copy constructor (deep copy) 5. Destructor (frees memory)
Test: Create DynamicArray obj1(10), then use obj1 to initialize obj2, and verify that the two objects' data point to different memory addresses.
- Default constructor: no parameters or all default parameters
- Initializer lists are more efficient than assignment in the constructor body
- Delegating constructors: one constructor calls another
- explicit prevents implicit type conversions
- Destructor: called automatically when an object is destroyed, releases resources
7. 🚀 Next Steps
Now that you've learned advanced constructor usage, let's move on to Operator Overloading (Lesson 30) — making custom types work with +, -, <<, and other operators!