Constructors, Destructors and Move Semantics — Interview Revision
Priority: HIGH — Constructors and move semantics are live-coding favorites Spoken answer target: under 45 seconds each
1. Constructors — Types Overview
Definition:
A constructor is a special member function with the same name as the class and no return type. It is called automatically when an object is created to initialize it.
Rules:
- Same name as class, no return type
- Can be overloaded (multiple constructors)
- If you define NO constructor, compiler generates a default
- If you define ANY constructor, compiler stops generating the default
2. Default Constructor
Definition:
A constructor that takes no parameters. Either explicitly written or compiler-generated if no other constructor is defined.
class Rectangle {
int width, height;
public:
Rectangle() : width(0), height(0) {} // Default constructor
int area() { return width * height; }
};
Rectangle r; // Default constructor calledTRAP: If you define a parameterized constructor, the compiler no longer generates a default:
class Box {
int side;
public:
Box(int s) : side(s) {} // Only this constructor
// Box b; // ERROR! No default constructor anymore
// Fix: add Box() = default;
};3. Parameterized Constructor
Definition:
Takes arguments to initialize the object with specific values. Use member initializer list — it is more efficient than assignment in the body.
class Player {
string name;
int health;
const int maxHealth; // const — MUST use initializer list
public:
// Initializer list: directly constructs members (efficient)
Player(string n, int h) : name(n), health(h), maxHealth(100) {
// Body: for additional logic only
}
// vs assignment in body (less efficient for strings/vectors):
// Player(string n, int h) {
// name = n; // First default-constructs, THEN assigns — wasteful
// health = h;
// }
};Why initializer list? For primitive types (int, double) it makes no difference. For complex types (string, vector), assignment in body: default-constructs first, then assigns — two operations. Initializer list: constructs directly with the value — one operation. Also REQUIRED for const and reference members.
4. Copy Constructor
Definition:
Creates a new object as a copy of an existing object. Signature:
ClassName(const ClassName& other). Compiler-generated default does SHALLOW copy — dangerous if class manages heap memory.
Shallow vs Deep Copy:
Shallow copy (default): both objects share the same pointer
s1.grades ---> [90, 85, 78] <--- s2.grades (SAME memory!)
Delete s1 -> s2.grades is a dangling pointer -> CRASH!
Deep copy (you write this): each object has its OWN copy
s1.grades ---> [90, 85, 78]
s2.grades ---> [90, 85, 78] (SEPARATE memory)class Student {
int* grades;
int count;
public:
Student(int c) : count(c), grades(new int[c]) {}
// Deep copy constructor
Student(const Student& other) : count(other.count), grades(new int[other.count]) {
for (int i = 0; i < count; i++)
grades[i] = other.grades[i]; // Copy VALUES, not the pointer
}
~Student() { delete[] grades; }
};
Student s1(5);
Student s2 = s1; // Deep copy constructor — separate memory
Student s3(s1); // Same thingLikely follow-up:
"When is the copy constructor called?" Three situations: (1)
Student s2 = s1;(initialization by copy), (2) passing an object by value to a function, (3) returning an object by value from a function (may be optimized away by RVO).
Common mistake: Forgetting to define a copy constructor when the class manages raw heap memory. The compiler's shallow copy will make two objects share the same pointer — double-delete on destruction causes undefined behavior.
5. Move Constructor — CRITICAL (C++11)
Definition:
A move constructor transfers resources from a temporary (rvalue) object instead of copying them. It steals the pointer and sets the source to null. Avoids expensive deep copies for temporaries.
Why it exists: Analogy: Copy = photocopying a 1000-page book (slow, uses paper). Move = handing the book to someone else (instant, original no longer has it).
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Copy constructor — expensive, allocates new memory
Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) {
copy(other.data, other.data + size, data);
cout << "COPIED";
}
// Move constructor — cheap, steals the pointer
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // CRITICAL: leave source in valid empty state
other.size = 0;
cout << "MOVED";
}
~Buffer() { delete[] data; } // delete nullptr is safe — no crash
};
int main() {
Buffer b1(100);
Buffer b2 = b1; // COPIED (b1 still valid, b2 has its own copy)
Buffer b3 = std::move(b1); // MOVED (b1 is now empty — don't use b1!)
// std::move doesn't actually move — it just CASTS b1 to an rvalue reference,
// enabling the move constructor to be selected by the compiler
}When is move constructor called?
Buffer b = std::move(existing_buffer);— explicit move- Returning a local variable from a function (RVO or move)
- Pushing a temporary into a vector:
vec.push_back(Buffer(100));
Likely follow-up:
"What does std::move actually do?" Nothing physically — it just casts its argument to an rvalue reference (T&&). This tells the compiler to select the move constructor/assignment instead of copy. The actual "moving" (pointer stealing) happens in the move constructor body.
Common mistake:
Using a moved-from object. After std::move(b1), b1's data pointer is null. Accessing b1.data causes undefined behavior. After a move, the source is in a "valid but unspecified state" — only destroy it or reassign it.
6. Delegating Constructors (C++11)
Definition:
A constructor that calls another constructor of the same class to avoid code duplication.
class Connection {
string host;
int port;
bool ssl;
public:
Connection(string h, int p, bool s) : host(h), port(p), ssl(s) {}
Connection(string h, int p) : Connection(h, p, false) {} // delegates
Connection(string h) : Connection(h, 80) {} // delegates
Connection() : Connection("localhost") {} // delegates
};
// Java equivalent:
// Connection() { this("localhost"); }
// Connection(String h) { this(h, 80); }7. Destructor
Definition:
A special member function (prefixed with ~) called automatically when an object is destroyed. Used to release resources acquired by the object.
Key Point:
- No parameters, no return type, cannot be overloaded — only ONE per class
- Called in REVERSE construction order (derived first, then base)
- RAII: acquire in constructor, release in destructor — resources cannot leak
class FileHandler {
FILE* file;
public:
FileHandler(const string& name) {
file = fopen(name.c_str(), "r");
}
~FileHandler() {
if (file) fclose(file); // ALWAYS runs, even if exception thrown!
}
};
{
FileHandler f("data.txt"); // Opens file
// ... use file
} // Destructor called automatically here — file closed, NO leak!8. Virtual Destructor — CRITICAL
Definition:
When a base class pointer points to a derived object and you delete it, a non-virtual destructor only runs the BASE class destructor — the derived destructor is SKIPPED, causing a resource leak.
class Base {
public:
virtual ~Base() { cout << "Base destroyed"; } // MUST be virtual!
};
class Derived : public Base {
int* data = new int[100];
public:
~Derived() {
delete[] data; // This MUST run to free memory
cout << "Derived destroyed";
}
};
Base* ptr = new Derived();
delete ptr;
// WITHOUT virtual: only "Base destroyed" — Derived's destructor skipped!
// data[] is NEVER freed — MEMORY LEAK
// WITH virtual: "Derived destroyed" then "Base destroyed" — correct!Rule: If a class has ANY virtual functions, it MUST have a virtual destructor. If a class is intended to be a base class, give it a virtual destructor.
Likely follow-up:
"Can a destructor throw an exception?" You should NEVER let a destructor throw. If a destructor throws during stack unwinding (exception already in progress), the program calls
std::terminate(). Always mark destructorsnoexcept(implicitly done in modern C++) and catch all exceptions inside them.
9. Rule of 3 / 5 / 0
Definition:
Guidelines for what special member functions to define when your class manages a resource.
Rule of 3 (C++03):
If you define ANY of: destructor, copy constructor, copy assignment
→ define ALL THREE. (If you need one, the default for others is likely wrong.)
Rule of 5 (C++11, adds move):
Also define: move constructor, move assignment operator
→ define all FIVE if you define any.
Rule of 0 (modern preference):
Design classes so that compiler-generated defaults work correctly.
Use RAII wrappers (string, vector, unique_ptr) — they handle their own cleanup.
Your class needs NO special member functions.Common mistake: Defining a destructor (to free memory) but forgetting the copy constructor. Compiler generates a SHALLOW copy constructor — two objects share the same pointer, double-free on destruction.
10. Constructor/Destructor Order in Inheritance
Construction: Base FIRST, then Derived (top-down, like laying foundations)
Destruction: Derived FIRST, then Base (bottom-up, like demolishing roof first)class A { public: A() { cout << "A"; } ~A() { cout << "~A"; } };
class B : public A { public: B() { cout << "B"; } ~B() { cout << "~B"; } };
class C : public B { public: C() { cout << "C"; } ~C() { cout << "~C"; } };
C obj;
// Construction: A → B → C
// Destruction: ~C → ~B → ~AQuick Self-Test — Constructors
- What is the member initializer list and why is it preferred?
- When does the compiler NOT generate a default constructor?
- What is shallow copy and why is it dangerous?
- What is the difference between copy constructor and move constructor?
- What does
std::moveactually do? - Why must a virtual destructor exist if you use polymorphism?
- What is the Rule of 5?
- What is a delegating constructor?
- In what order are constructors called in a hierarchy A->B->C?
- After
std::move(obj), what state isobjin?
Answers
- Initializer list directly constructs members (one step). Assignment in body default-constructs first then assigns (two steps). Required for const and reference members.
- When you explicitly define any constructor — the compiler stops generating the default constructor.
- Both objects share the same pointer. When one is destroyed, the other has a dangling pointer — crash on access or double-free.
- Copy constructor:
(const T& other)— allocates new memory and copies. Move constructor:(T&& other)— steals the pointer, sets source to null. Move is O(1) regardless of size. - Nothing physical — it casts an lvalue to an rvalue reference (T&&), enabling the move constructor to be selected by the compiler.
- Without it,
delete basePtronly calls base destructor — derived destructor is skipped, causing memory leaks. - If you define destructor, copy constructor, or copy assignment, also define move constructor and move assignment (total of 5 special functions).
- A constructor that calls another constructor of the same class using
: ClassName(args)syntax — avoids code duplication. - A first, then B, then C (base to derived — top down).
- Valid but unspecified state — the pointer inside is null. You can only safely destroy or reassign it. Do NOT use it otherwise.