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.

CPP
#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

CPP
#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).

TEXT 📖 Display only
// 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):

CPP
Student(const std::string& n, int a) {
 name = n; // This is assignment, not initialization
 age = a;
}

Initializer list approach (recommended):

TEXT 📖 Display only
Student(const std::string& n, int a) : name(n), age(a) {
 // Function body can be empty
}

💡 Advantages:

  1. More efficient (direct initialization, not default construction followed by assignment)
  2. Cases where initializer list is required:

(2) 2.2 Cases Where Initializer List Is Required

Case 1: const members

CPP
#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:

  1. Initializing one object from another
  2. Passing an object by value to a function (copies)
  3. Returning an object from a function (may copy)

▶ Example 1: Default Copy Constructor (Difficulty ⭐)

CPP
#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;
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
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

CPP
#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.

TEXT 📖 Display only
#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

CPP
#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.

CPP
#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 ⭐⭐)

CPP
#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;
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
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 ⭐)

CPP
#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;
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Zhang San, 20 years old
Li Si, 18 years old
Unknown, 18 years old
💡 Tip: Delegating constructors let one constructor call another, avoiding code duplication.



📖 Summary

📝 Exercises

  1. Basic (Difficulty ⭐): Define a Book class with two members: title and author. Write the constructor using an initializer list.

  2. Intermediate (Difficulty ⭐⭐): Define a custom copy constructor for the Book class that implements deep copy (assume the title is stored as char*, requiring new memory allocation).

  3. Challenge (Difficulty ⭐⭐⭐): Define a DynamicArray class (dynamic array) with:

  4. int* data (pointing to heap memory)

  5. 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.


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!

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏