C++ smart pointers and memory interview questions, with answers
Modern C++ manages memory without garbage collection and almost without delete, and interviews check that you know how. The questions are about what new and delete really do, why a destructor that runs at a predictable moment changes everything, which smart pointer owns what, and how shared ownership can still leak. Each answer below comes with code that was compiled and run, and every object it creates is released.
The questions start with raw new and delete, move through RAII and the three smart pointers, and end with the factory functions and custom deleters that complete the picture. Then take the free C++ diagnostic — ten questions across every C++ topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What is the difference between delete and delete[] in C++?
In short: Memory from new must be released with delete and memory from new[] with delete[]; mixing them is undefined behaviour, because delete[] must run a destructor for every element.
new T allocates one object and constructs it; new T[n] allocates an array and constructs n elements, and the runtime usually stores the count so that delete[] knows how many destructors to call. delete p calls one destructor and frees one object's memory, so using it on an array skips the other destructors and may pass the allocator a pointer it never returned, while using delete[] on a single object reads a count that was never stored. Both mismatches are undefined behaviour, and AddressSanitizer reports them as alloc-dealloc mismatches. The practical answer is to avoid both: std::vector<T> replaces new T[], and std::unique_ptr<T[]> calls delete[] for you, as below.
struct Cell { ~Cell() { std::cout << "~"; } }; int main() { Cell* raw = new Cell[3]; delete[] raw; std::cout << " "; auto a = std::make_unique< Cell[]>(2); } // ~~~ ~~
2.What is RAII, and why is it the basis of resource management in C++?
In short: Resource acquisition is initialisation: an object acquires a resource in its constructor and releases it in its destructor, so the release happens automatically when the object's scope ends, even through an exception.
C++ destroys local objects deterministically, at the end of their scope, in reverse order of construction, and it does so on every exit path: a normal return, an early return, or an exception unwinding the stack. RAII ties each resource to such an object, so memory, files, locks and sockets are released by destructors rather than by cleanup code that someone must remember to call. std::unique_ptr releases memory, std::lock_guard unlocks a mutex and std::ifstream closes its file this way, and the guard below prints freed even though the function exits by throwing. The consequence for design is that a destructor should never throw, since it may already be running during stack unwinding.
struct Guard { ~Guard() { std::cout << "freed\n"; } }; void work() { Guard g; throw std::runtime_error( "failed"); } int main() { try { work(); } catch (...) { std::cout << "caught"; } } // freed // caught
3.What is std::unique_ptr, and why can it only be moved, not copied?
In short: unique_ptr is the sole owner of a heap object and deletes it when it goes out of scope; copying would create two owners, so its copy operations are deleted and ownership is transferred with std::move.
std::unique_ptr<T> holds a pointer and deletes the object in its destructor, with no overhead over a raw pointer in the common case. Its whole contract is single ownership, so the copy constructor and copy assignment are deleted: two copies would each delete the same object. Ownership can be handed over explicitly, with std::move, after which the source is empty; a function that returns a unique_ptr does this automatically, which makes it the natural return type for factory functions. It works with incomplete types, supports arrays as unique_ptr<T[]>, and converts to shared_ptr if shared ownership is needed later. Use it by default whenever an object has one clear owner.
std::unique_ptr<std::string> make(const char* s) { return std::make_unique< std::string>(s); } int main() { auto a = make("owner"); auto b = std::move(a); std::cout << (a == nullptr) << ' ' << *b; } // 1 owner
4.How does std::shared_ptr decide when to delete its object?
In short: Every shared_ptr to the object shares one control block holding a reference count; copying increments it, destroying or resetting decrements it, and the object is deleted when it reaches zero.
std::shared_ptr adds a control block to the object it manages, holding the strong count of shared_ptrs, a weak count, and the deleter. Copying a shared_ptr increments the strong count and destroying or resetting one decrements it, with atomic operations, so the counting is thread-safe, although the object itself is not. When the strong count reaches zero the object is deleted, and the control block goes when the weak count also reaches zero. use_count() reports the strong count and is useful for demonstrations, as below, but not for logic in threaded code. Shared ownership has real costs, the control block, the atomic updates and the risk of cycles, so prefer unique_ptr when one owner is enough.
auto a = std::make_shared< std::string>("shared"); std::shared_ptr<std::string> b; b = a; std::cout << a.use_count(); b.reset(); std::cout << a.use_count(); // 21
5.What problem does std::weak_ptr solve?
In short: It observes an object owned by shared_ptrs without owning it, which breaks reference cycles and lets a cache or observer check whether the object still exists before using it.
Two objects that hold shared_ptrs to each other keep each other's count above zero, so neither is ever deleted, even after every outside pointer is gone: shared ownership leaks in cycles. A weak_ptr refers to the object without adding to the strong count, so a parent can own its children through shared_ptr while each child points back to its parent through weak_ptr, and the whole structure is freed normally. To use the object, call lock(), which returns a shared_ptr that is empty if the object has already been destroyed, as below, or check expired(). The same shape suits caches and observers, which want to use an object only while someone else keeps it alive.
std::weak_ptr<int> w; { auto s = std::make_shared< int>(7); w = s; if (auto p = w.lock()) std::cout << *p << ' '; } std::cout << w.expired(); // 7 1
6.Why prefer std::make_unique and std::make_shared to calling new?
In short: They never expose a raw owning pointer, so nothing can leak between the allocation and the smart pointer taking ownership, and make_shared allocates the object and its control block together.
Writing std::shared_ptr<T>(new T) allocates twice, once for the object and once for the control block, while make_shared allocates both in one block, which saves a call and improves locality. Both factory functions also keep new out of application code entirely, so every allocation is owned from the instant it exists; before C++17's tighter rules on evaluating function arguments, f(std::shared_ptr<T>(new T), g()) could leak if g() threw after new ran but before the shared_ptr was built. The trade-offs are small but real: make_shared's single block means the object's memory is only freed when the last weak_ptr also goes, and neither function can take a custom deleter, which needs the constructor form.
struct Job { std::string name; int priority; }; int main() { auto j = std::make_unique< Job>("backup", 2); std::cout << j->name << ' ' << j->priority; } // backup 2
7.What is a custom deleter, and when does a smart pointer need one?
In short: A custom deleter is the function a smart pointer calls instead of delete, used when the resource must be released some other way, such as fclose for a FILE* or a C library's free function.
unique_ptr and shared_ptr default to delete, but many resources are released differently: a FILE* from fopen needs fclose, memory from malloc needs free, and C libraries have their own destroy functions. A custom deleter makes RAII work for all of them. For unique_ptr the deleter's type is part of the pointer type, as in std::unique_ptr<FILE, decltype(&std::fclose)>, and a stateless function object or lambda adds no size, whereas a function pointer adds one pointer. For shared_ptr the deleter is passed to the constructor and stored in the control block, so it does not change the type. The deleter below runs when the pointer goes out of scope, before the message printed after it.
struct Closer { void operator()(int* p) const { std::cout << "close "; std::free(p); } }; using IntBox = std::unique_ptr<int, Closer>; IntBox make() { void* m = std::malloc(4); return IntBox( static_cast<int*>(m)); } int main() { { IntBox p = make(); } std::cout << "done"; } // close done
How the diagnostic asks it
One question from the C++ bank, exactly as a sitting would show it. The bank has 5 on memory & smart pointers and 30 across C++.
What does this C++ code print?
auto a = std::make_shared<int>(1); auto b = a; { auto c = a; std::cout << a.use_count(); } std::cout << a.use_count();
- 132correct
- 233
- 311
- 421
use_count() reports how many shared_ptr objects currently share ownership of the int. Inside the block, a, b and c all own it, so it prints 3. When the block ends, c is destroyed and the count drops to 2, giving 32. 33 forgets that c's destructor gives up its share. 11 treats copying a shared_ptr as copying the int, so that each pointer has its own; copies share one object and one count. 21 counts only the other owners, not a itself. When the last owner goes, the count reaches 0 and the int is deleted.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 C++ questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.