oops-03-constructors-destructors.md

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 called

TRAP: 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 thing

Likely 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?

  1. Buffer b = std::move(existing_buffer); — explicit move
  2. Returning a local variable from a function (RVO or move)
  3. 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 destructors noexcept (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 → ~A

Quick Self-Test — Constructors

  1. What is the member initializer list and why is it preferred?
  2. When does the compiler NOT generate a default constructor?
  3. What is shallow copy and why is it dangerous?
  4. What is the difference between copy constructor and move constructor?
  5. What does std::move actually do?
  6. Why must a virtual destructor exist if you use polymorphism?
  7. What is the Rule of 5?
  8. What is a delegating constructor?
  9. In what order are constructors called in a hierarchy A->B->C?
  10. After std::move(obj), what state is obj in?

Answers

  1. Initializer list directly constructs members (one step). Assignment in body default-constructs first then assigns (two steps). Required for const and reference members.
  2. When you explicitly define any constructor — the compiler stops generating the default constructor.
  3. Both objects share the same pointer. When one is destroyed, the other has a dangling pointer — crash on access or double-free.
  4. 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.
  5. Nothing physical — it casts an lvalue to an rvalue reference (T&&), enabling the move constructor to be selected by the compiler.
  6. Without it, delete basePtr only calls base destructor — derived destructor is skipped, causing memory leaks.
  7. If you define destructor, copy constructor, or copy assignment, also define move constructor and move assignment (total of 5 special functions).
  8. A constructor that calls another constructor of the same class using : ClassName(args) syntax — avoids code duplication.
  9. A first, then B, then C (base to derived — top down).
  10. Valid but unspecified state — the pointer inside is null. You can only safely destroy or reassign it. Do NOT use it otherwise.