What seems like a simple constructor hides countless dangers; how many can you avoid?
Recently, I came across a C++ constructor problem during a code review. It appears unremarkable but has caused many programmers to falter. Today, let’s uncover the 3 fatal traps of this “death constructor”!
Original code: Under the calm surface, dark currents surge
#include <iostream>#include <cstring>using namespace std;
class Student {private: char* name; int age; double* scores; int scoreCount;
public: // Problematic constructor Student(const char* n, int a, double* sc, int count) { name = new char[strlen(n) + 1]; strcpy(name, n); age = a;
scores = new double[count]; for(int i = 0; i < count; i++) { scores[i] = sc[i]; } scoreCount = count; }
void display() { cout << "Name: " << name << ", Age: " << age << endl; cout << "Scores: "; for(int i = 0; i < scoreCount; i++) { cout << scores[i] << " "; } cout << endl; }};
Does it look normal? Let’s uncover the traps step by step!
Trap 1: The Ghost of Memory Leak
Problem: The class has dynamic memory allocation but lacks a destructor!
// Missing destructor!// Every time a Student object is created, there will be a memory leak~Student() { delete[] name; // Correct: use delete[] for arrays delete[] scores; // Correct: use delete[] for arrays}
Fatal consequence: The longer the program runs, the more severe the memory leak becomes, ultimately leading to system crashes.
Trap 2: The Minefield of Shallow Copy
Problem: No custom copy constructor and assignment operator!
// Disaster strikes when copying!Student s1("Zhang San", 18, grades, 3);Student s2 = s1; // Shallow copy! Both objects point to the same memory
// When s1 and s2 are destructed, the same memory will be deleted twice → Program crash!
Solution: Implement deep copy
// Copy constructorStudent(const Student& other) { name = new char[strlen(other.name) + 1]; strcpy(name, other.name); age = other.age;
scoreCount = other.scoreCount; scores = new double[scoreCount]; for(int i = 0; i < scoreCount; i++) { scores[i] = other.scores[i]; }}
// Copy assignment operatorStudent& operator=(const Student& other) { if(this != &other) { // Prevent self-assignment delete[] name; // Release original resources delete[] scores;
name = new char[strlen(other.name) + 1]; strcpy(name, other.name); age = other.age;
scoreCount = other.scoreCount; scores = new double[scoreCount]; for(int i = 0; i < scoreCount; i++) { scores[i] = other.scores[i]; } } return *this;}
Trap 3: The Invisible Killer of Exception Safety
Problem: If new fails in the constructor, the allocated memory cannot be released!
Student(const char* n, int a, double* sc, int count) { name = new char[strlen(n) + 1]; // May fail strcpy(name, n); age = a;
scores = new double[count]; // If this fails, memory leak for name! for(int i = 0; i < count; i++) { scores[i] = sc[i]; } scoreCount = count;}
Solution: Use smart pointers or exception-safe writing
// Method 1: Use smart pointers (C++11 and above)#include <memory>class Student {private: std::unique_ptr<char[]> name; std::unique_ptr<double[]> scores; // ... other members;}
// Method 2: Traditional exception-safe writingStudent(const char* n, int a, double* sc, int count) { char* tempName = nullptr; double* tempScores = nullptr;
try { tempName = new char[strlen(n) + 1]; strcpy(tempName, n);
tempScores = new double[count]; for(int i = 0; i < count; i++) { tempScores[i] = sc[i]; }
// Only assign to member variables if all allocations succeed name = tempName; scores = tempScores; age = a; scoreCount = count;
} catch(...) { delete[] tempName; // Clean up temporary resources delete[] tempScores; throw; // Rethrow exception }}
Feel free to share your experiences with C++ object-oriented programming in the comments!