C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
215: Common C++ Interview Questions on Memory and Objects
I've sat through dozens of technical interviews over the last few years, and there is a specific set of "gotcha" questions regarding memory and objects that come up almost every single time. Most candidates know the theory, but they stumble when asked to explain why something is happening in a live code snippet. Instead of giving you a list of definitions, let's just break some things and see why the compiler (or the runtime) is fighting us.
The Disappearing Destructor
Let's start with something that looks innocent. I'm building a simple game engine, and I have a base class for all game entities. I'll create a specific Player class that manages its own memory—maybe a buffer for inventory items.
#include <iostream>
#include <vector>
class Entity {
public:
~Entity() { std::cout << "Entity destroyed\n"; }
};
class Player : public Entity {
std::vector<int> inventory;
public:
Player() : inventory(1000, 1) {} // Allocate some memory
~Player() { std::cout << "Player destroyed\n"; }
};
int main() {
Entity* e = new Player();
delete e;
return 0;
}
If you run this, you'll see "Entity destroyed" printed to the console. But wait—where is "Player destroyed"? I instantiated a Player, but the Player destructor never ran. This is a classic interview trap. Because the pointer is of type Entity* and the destructor isn't virtual, the compiler performs static binding. It sees a pointer to an Entity and calls the Entity destructor. Period.
The inventory vector in the Player class is now leaking. To fix this, I need to tell the compiler, "Hey, if this object is actually a derived type, find that derived destructor first." I'll add virtual to the base destructor:
class Entity {
public:
virtual ~Entity() { std::cout << "Entity destroyed\n"; }
};
Now, when I run it, the output is "Player destroyed" followed by "Entity destroyed". That's the core of the "Virtual Destructor" question. If you're deleting a derived object through a base pointer, the base destructor must be virtual.
Where did my data go?
Now let's look at something more subtle: Object Slicing. I'll write a function that takes an Entity and prints its status. I'll pass it by value, which seems natural enough if I just want a copy to work with.
void printEntityInfo(Entity e) {
// Imagine some logic here that checks if the entity is a Player
std::cout << "Processing entity...\n";
}
int main() {
Player p;
printEntityInfo(p);
}
At first glance, this works. But if Player had added new member variables or overridden virtual functions, those wouldn't exist inside printEntityInfo. Why? Because the function signature asks for an Entity object, not a reference. The compiler literally "slices" the Player object, stripping away everything that makes it a Player, and copies only the Entity part into the function's parameter.
In an interview, if they ask you about slicing, they want to hear that you understand the difference between passing by value and passing by reference (or pointer). To keep the "Player-ness" of the object, I have to change the signature to void printEntityInfo(Entity& e). Now, I'm passing a reference to the original object rather than creating a sliced copy.
The Hidden Memory Cost
One last thing. I've noticed some candidates get confused when they check the size of their objects. Let's try this experiment. I'll create a lean base class and check its size.
class Simple {
int x;
};
class VirtualSimple {
int x;
virtual void foo() {}
};
int main() {
std::cout << "Simple: " << sizeof(Simple) << "\n";
std::cout << "VirtualSimple: " << sizeof(VirtualSimple) << "\n";
}
On a 64-bit system, Simple is 4 bytes. But VirtualSimple is 16 bytes (usually). Where did those extra 12 bytes come from? I only added one function!
The moment I added that virtual keyword, the compiler injected a hidden pointer into the class: the vptr (virtual pointer). This pointer points to a vtable (virtual table), which is a static array used to look up the correct function implementation at runtime. This is the "magic" that makes polymorphism work, but it comes with a memory cost per object. If you're working on an embedded system with tiny amounts of RAM, these hidden pointers actually matter.
📋 Practical Task
Fixing the Leaky Particle System
You have been handed a piece of code for a particle system. The developer who wrote it forgot about the concepts we just discussed. The system creates various types of particles (FireParticle, WaterParticle) and stores them in a std::vector<Particle*>. When the system shuts down, it deletes the particles, but the memory associated with the specific particle types is leaking.
Your Task: Modify the provided code to ensure that the derived particle destructors are called correctly and prevent memory leaks. Then, add a std::cout statement inside the FireParticle destructor to prove it is being executed.
#include <iostream>
#include <vector>
class Particle {
public:
~Particle() {
std::cout << "Particle Base Cleaned\n";
}
};
class FireParticle : public Particle {
int* heatMap = new int[100]; // Simulated heavy resource
public:
~FireParticle() {
delete[] heatMap;
// TODO: Add confirmation print here
}
};
class WaterParticle : public Particle {
int* moistureMap = new int[100]; // Simulated heavy resource
public:
~WaterParticle() {
delete[] moistureMap;
}
};
int main() {
std::vector<Particle*> system;
system.push_back(new FireParticle());
system.push_back(new WaterParticle());
for (auto p : system) {
delete p;
}
return 0;
}
There are no comments for now.