Language-Specific Deep Dives
C++ is a language renowned for its performance and control. It gives the programmer direct access to memory and low-level hardware features, making it the language of choice for systems programming, game development, high-frequency trading, and other performance-critical domains.
This power comes with responsibility. A deep understanding of C++'s memory management and object lifecycle is essential for writing correct and safe code. This section covers some of the most critical C++ concepts that are frequently tested in interviews.
This is arguably the most important C++ idiom.
The Problem: In C++, you are responsible for manual memory management. If you allocate memory with new, you must remember to deallocate it with delete. Forgetting to do so causes memory leaks. More than just memory, you might also need to manage file handles, network sockets, or mutex locks. Remembering to release these resources, especially in the face of exceptions, is very difficult and error-prone.
The Solution: Resource Acquisition Is Initialization (RAII) is a design pattern that ties the lifecycle of a resource to the lifetime of an object.
Since C++ guarantees that the destructor of a stack-allocated object is always called when it goes out of scope (whether by normal exit or by an exception), the resource is guaranteed to be cleaned up.
Smart Pointers are the embodiment of RAII for memory management. They are wrapper classes that hold a raw pointer and automatically deallocate the memory when the smart pointer itself is destroyed.
std::unique_ptr<T>: Represents unique ownership.
unique_ptr can point to a given object at a time.unique_ptr to another using std::move.std::shared_ptr<T>: Represents shared ownership.
shared_ptr instances can point to the same object.shared_ptrs are pointing to the object.shared_ptr is destroyed.std::weak_ptr<T>: A non-owning "observer" of a shared_ptr.
shared_ptr but does not increase the reference count.shared_ptrs.shared_ptr. If the underlying object has already been destroyed, the lock will fail.#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass Created\n"; }
~MyClass() { std::cout << "MyClass Destroyed\n"; }
};
void use_unique_ptr() {
// Memory is allocated here.
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
// std::unique_ptr<MyClass> ptr2 = ptr1; // COMPILE ERROR: Cannot copy.
// Ownership is transferred to ptr2. ptr1 is now null.
std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
} // ptr2 goes out of scope here, and the MyClass object is automatically destroyed.
void use_shared_ptr() {
std::shared_ptr<MyClass> ptr1;
{
// Memory is allocated, ref count is 1.
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();
// Both pointers share ownership, ref count is 2.
ptr1 = ptr2;
} // ptr2 goes out of scope, ref count becomes 1.
} // ptr1 goes out of scope, ref count becomes 0, object is destroyed.
Interview Takeaway: RAII is the fundamental C++ pattern for resource management. Smart pointers are the implementation of RAII for memory. Default to unique_ptr for performance and clear ownership, and use shared_ptr only when shared ownership is truly necessary.
This is a rule of thumb for C++ class design that relates to RAII and copy semantics.
The Rule of Three: If a class needs a user-defined destructor, a copy constructor, or a copy assignment operator, then it almost certainly needs all three.
The Rule of Five: With the introduction of move semantics in C++11, the rule was extended. If a class defines any of the "big three," or a move constructor, or a move assignment operator, it should probably define or delete all five.
The Rule of Zero: This is the modern ideal. Design your classes so that they don't manage any raw resources directly. Instead, use existing resource-managing classes (like std::string, std::vector, and smart pointers). If your class is just a composition of these well-behaved types, you don't need to write any of the "big five" yourself. The compiler-generated defaults will work correctly.
Interview Takeaway: The Rule of Zero is the goal. Structure your classes to use standard library containers and smart pointers to manage resources. If you must manage a resource manually, you must follow the Rule of Five and carefully define the copy and move semantics for your class.
C++ supports runtime polymorphism through virtual functions.
virtual, you are telling the compiler that this function may be overridden by a derived class. When you call this function through a base class pointer or reference, the compiler will generate code to look up the correct version of the function to call at runtime based on the actual type of the object. This is called dynamic dispatch.Key Keywords:
virtual: In the base class, declares a function as being overridable.override: In the derived class, indicates that you are intentionally overriding a base class virtual function. This is not mandatory but is strongly recommended as it allows the compiler to catch errors (e.g., if you misspell the function name).final: In the derived class, indicates that this function cannot be overridden any further down the inheritance chain.virtual. This ensures that when you delete a derived class object through a base class pointer, the correct destructor (the derived one, followed by the base one) is called. Forgetting this leads to resource leaks.#include <iostream>
#include <memory>
class Animal {
public:
// A virtual destructor is essential for a base class.
virtual ~Animal() = default;
virtual void speak() const {
std::cout << "Animal speaks\n";
}
};
class Dog : public Animal {
public:
// 'override' ensures we are correctly overriding a base virtual function.
void speak() const override {
std::cout << "Dog barks\n";
}
};
int main() {
// Polymorphism in action.
// 'animal_ptr' is a base pointer, but it points to a derived object.
std::unique_ptr<Animal> animal_ptr = std::make_unique<Dog>();
// Because speak() is virtual, the Dog::speak() version is called at runtime.
animal_ptr->speak(); // Prints "Dog barks"
return 0;
} // The virtual destructor ensures Dog's destructor is called, then Animal's.
Interview Takeaway: Virtual functions are C++'s mechanism for runtime polymorphism. Explain how they work using the concept of a vtable and dynamic dispatch. Emphasize the critical importance of a virtual destructor in any base class to prevent resource leaks.