Skip to Content
Course content

215: Common C++ Interview Questions on Memory and Objects

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.