Smart Pointers in C++
Smart Pointers in C++ Interview with follow-up questions
1. What are smart pointers in C++ and how do they differ from traditional pointers?
Smart pointers are class templates in `` that wrap a raw pointer and add automatic lifetime management. They follow RAII: the owned object is destroyed (and memory freed) when the smart pointer itself is destroyed — even if an exception is thrown.
The three standard smart pointers:
| Type | Ownership model | Header |
|---|---|---|
std::unique_ptr |
Sole ownership — one owner at a time | `` |
std::shared_ptr |
Shared ownership — reference-counted | `` |
std::weak_ptr |
Non-owning observer of a shared_ptr |
`` |
Key differences from raw pointers:
| Aspect | Raw pointer | Smart pointer |
|---|---|---|
| Memory management | Manual delete required |
Automatic on destruction |
| Ownership semantics | Not enforced | Enforced by type |
| Copyability | Freely copyable | unique_ptr is move-only; shared_ptr reference-counts copies |
| Exception safety | Leaks if exception before delete |
Safe — destructor always runs |
| Null safety | No built-in check | Same risk, but operator bool is provided |
Modern C++ best practices (C++14 and later):
- Default to
unique_ptr— it has zero overhead over a raw pointer and expresses sole ownership clearly. - Create with factory functions, not raw
new:cpp auto p = std::make_unique(args...); // C++14 auto s = std::make_shared(args...); // C++14make_sharedperforms one allocation for both the object and the reference-count block (more efficient thanshared_ptr(new T(...))). - Avoid raw owning pointers —
T*should appear only as non-owning observers (borrowing). unique_ptris move-only. Transfer ownership withstd::move:cpp auto p2 = std::move(p); // p is now null; p2 owns the object- Rule of Zero: if all your class members are smart pointers or standard containers, you need no user-defined destructor, copy constructor, or copy-assignment — the compiler-generated versions are correct.
Raw pointers still have a role as non-owning handles (e.g., a function parameter that just observes an object without claiming ownership), but should never be the sole owner of heap memory in modern C++.
Follow-up 1
Can you explain how unique_ptr works in C++?
unique_ptr is a type of smart pointer in C++ that provides exclusive ownership of the dynamically allocated object. It ensures that only one unique_ptr can point to a particular object at a time. When a unique_ptr goes out of scope or is reset, it automatically deletes the associated object. Here's an example:
#include
int main() {
std::unique_ptr ptr(new int(42));
// Use ptr
// ...
return 0;
}
Follow-up 2
What is the significance of shared_ptr in C++?
shared_ptr is another type of smart pointer in C++ that provides shared ownership of the dynamically allocated object. It allows multiple shared_ptr instances to point to the same object. The object is automatically deleted when the last shared_ptr pointing to it goes out of scope or is reset. This helps in managing resources that need to be shared among multiple parts of the code. Here's an example:
#include
int main() {
std::shared_ptr ptr1(new int(42));
std::shared_ptr ptr2 = ptr1;
// Use ptr1 and ptr2
// ...
return 0;
}
Follow-up 3
How does weak_ptr help in preventing memory leaks?
weak_ptr is a type of smart pointer in C++ that provides a non-owning reference to an object managed by shared_ptr. It allows you to observe the object without affecting its lifetime. Unlike shared_ptr, weak_ptr does not contribute to the reference count of the object. This helps in preventing circular dependencies and potential memory leaks. Here's an example:
#include
int main() {
std::shared_ptr ptr1(new int(42));
std::weak_ptr ptr2 = ptr1;
// Use ptr1 and ptr2
// ...
return 0;
}
Follow-up 4
Can you explain the concept of ownership in context of smart pointers?
In the context of smart pointers, ownership refers to the responsibility of managing the lifetime of dynamically allocated objects. Smart pointers provide a way to express and enforce ownership semantics. For example, unique_ptr enforces exclusive ownership, where only one unique_ptr can own an object at a time. shared_ptr enforces shared ownership, where multiple shared_ptr instances can own the same object. weak_ptr provides a non-owning reference to an object without affecting its lifetime. By using smart pointers, you can clearly define and manage ownership relationships, which helps in preventing memory leaks and improving code safety.
Follow-up 5
What are the advantages of using smart pointers over traditional pointers?
Using smart pointers in C++ has several advantages over traditional pointers:
- Automatic memory management: Smart pointers automatically deallocate memory when they go out of scope or are no longer needed, preventing memory leaks.
- Improved code safety: Smart pointers provide ownership semantics, making it clear who is responsible for managing the lifetime of dynamically allocated objects.
- Easier resource management: Smart pointers can be used to manage resources other than memory, such as file handles or network connections.
- Compatibility with standard library algorithms: Smart pointers can be used with standard library algorithms that expect iterators, making them easier to integrate into existing code.
- Customizable deletion behavior: Smart pointers allow you to customize the deletion behavior by providing a custom deleter, which can be useful in certain scenarios. Overall, smart pointers help in writing safer and more maintainable code by automating memory management and enforcing ownership semantics.
2. How does a unique_ptr work in C++?
std::unique_ptr is a smart pointer that expresses sole, exclusive ownership of a heap-allocated object. Exactly one unique_ptr owns the managed object at any time; when that pointer is destroyed or reset, the object is deleted automatically.
Core mechanics:
- Not copyable — copying would create two owners, violating the sole-ownership invariant. The copy constructor and copy-assignment operator are deleted.
- Movable — ownership transfers with
std::move, after which the source becomes null. - Zero overhead — with a default deleter,
unique_ptrcompiles down to the same machine code as a raw pointer; there is no reference count or extra heap allocation.
Creating one — always use make_unique (C++14):
#include
auto p = std::make_unique(42); // single object
auto arr = std::make_unique(10); // array of 10 ints
auto obj = std::make_unique(arg1, arg2); // args forwarded to constructor
Prefer make_unique over raw new — it is exception-safe and never exposes the object as a raw owning pointer.
Transferring ownership:
auto p1 = std::make_unique();
auto p2 = std::move(p1); // p2 owns the object; p1 is now null
Common operations:
p.get() // non-owning raw pointer; pass to C APIs that take T*
p.reset() // destroy owned object; p becomes null
p.release() // relinquish ownership; returns raw pointer -- caller must delete
*p // dereference
p->member // member access
if (p) { ... } // null check
Custom deleters: a second template argument specifies the deleter, useful for non-memory resources:
auto file = std::unique_ptr(fopen("x.txt", "r"), &fclose);
// fclose is called automatically when file goes out of scope
When to use unique_ptr:
- Default choice for any heap-allocated object with a single clear owner.
- Factory functions returning a newly created object.
- Class members that own heap-allocated sub-objects.
Rule of Zero: if all your class members are unique_ptr instances or standard containers, the compiler-generated destructor, move constructor, and move-assignment operator are correct — no user-defined special members needed.
Follow-up 1
Can you share a code snippet demonstrating the use of unique_ptr?
Certainly! Here's an example code snippet that demonstrates the use of unique_ptr:
#include
int main() {
std::unique_ptr ptr(new int(42));
std::cout << *ptr << std::endl; // Output: 42
return 0;
}
In this example, a unique_ptr is created and initialized with a dynamically allocated integer. The value of the integer is then printed using the dereference operator (*). When the unique_ptr goes out of scope, it automatically deletes the integer.
Follow-up 2
What happens when you try to make a copy of a unique_ptr?
A unique_ptr is non-copyable. When you try to make a copy of a unique_ptr, a compilation error occurs. This is because copying a unique_ptr would result in multiple owners for the same managed object, which violates the unique ownership guarantee provided by unique_ptr. To transfer ownership of a unique_ptr, you can use std::move.
Follow-up 3
How can you transfer the ownership of a unique_ptr?
To transfer the ownership of a unique_ptr, you can use the std::move function. std::move converts the unique_ptr into an rvalue, allowing it to be moved rather than copied. Here's an example:
std::unique_ptr ptr1(new int(42));
std::unique_ptr ptr2 = std::move(ptr1);
In this example, the ownership of the dynamically allocated integer is transferred from ptr1 to ptr2 using std::move. After the transfer, ptr1 is in a valid but unspecified state, and ptr2 becomes the sole owner of the integer.
Follow-up 4
What is the significance of std::move in context of unique_ptr?
In the context of unique_ptr, std::move is used to transfer ownership of the managed object from one unique_ptr to another. It does this by converting the unique_ptr into an rvalue, which allows it to be moved rather than copied. This is important because unique_ptr is non-copyable, so without std::move, you would not be able to transfer ownership of the managed object. std::move is a utility function provided by the C++ standard library in the header.
3. Explain the concept of shared_ptr in C++.
std::shared_ptr is a smart pointer that implements shared ownership through reference counting. Multiple shared_ptr instances can point to the same object; the object is destroyed and its memory freed only when the last owning shared_ptr is destroyed or reset.
Internal structure:
A shared_ptr maintains two pointers internally:
- A pointer to the managed object.
- A pointer to a control block on the heap, which holds the strong reference count, a weak reference count (for
weak_ptr), and an optional deleter.
When the strong count drops to zero, the object is destroyed. When the weak count also drops to zero, the control block itself is freed.
Creating — always use make_shared (C++14):
#include
auto s1 = std::make_shared(args...);
auto s2 = s1; // both s1 and s2 own the object; ref count is 2
make_shared performs a single allocation for both the object and the control block, which is more efficient than std::shared_ptr(new T(...)) (which allocates twice).
Reference count inspection (mainly for debugging):
s1.use_count() // current strong reference count (avoid basing logic on this)
Cycle problem and weak_ptr:
If two objects hold shared_ptr references to each other, the reference count never reaches zero — a memory leak. Break cycles with std::weak_ptr:
struct Node {
std::shared_ptr next;
std::weak_ptr prev; // non-owning back-link
};
Thread safety: the reference count is updated atomically, so copying and destroying shared_ptr instances from different threads is safe. However, the managed object itself is not protected — you still need synchronization if multiple threads read/write through the pointer.
When to use shared_ptr:
- Multiple independent components genuinely share ownership of the same object and lifetimes are not strictly nested.
- Graphs, caches, or any structure where ownership is dynamic.
Prefer unique_ptr when in doubt. Shared ownership is harder to reason about, and the reference-counting overhead (typically two atomic increments/decrements per copy) is unnecessary when a single owner suffices. Only reach for shared_ptr when you actually need multiple owners.
Follow-up 1
How does shared_ptr manage the memory?
shared_ptr manages the memory by using reference counting. Each shared_ptr object maintains a reference count, which is incremented when a new shared_ptr is created that points to the same object, and decremented when a shared_ptr is destroyed or reset. When the reference count reaches zero, the object is deleted.
Follow-up 2
What is reference counting in context of shared_ptr?
Reference counting is a technique used by shared_ptr to keep track of how many shared_ptr objects are currently pointing to the same object. Each shared_ptr object maintains a reference count, which is incremented when a new shared_ptr is created that points to the same object, and decremented when a shared_ptr is destroyed or reset. When the reference count reaches zero, the object is deleted.
Follow-up 3
Can you share a scenario where shared_ptr is more suitable than unique_ptr?
shared_ptr is more suitable than unique_ptr in scenarios where multiple objects need to share ownership of a dynamically allocated object. For example, in a graph data structure where nodes can have multiple parents, shared_ptr can be used to manage the memory of the nodes. Each node can have shared_ptr objects pointing to it, and the memory will be automatically deleted when all the shared_ptr objects are destroyed or reset.
Follow-up 4
How does shared_ptr handle cyclic references?
shared_ptr uses a technique called weak references to handle cyclic references. A weak reference is a non-owning reference to an object managed by shared_ptr. It does not contribute to the reference count, and does not prevent the object from being deleted. By using weak references, shared_ptr can break cyclic references and ensure that the memory is properly managed. Weak references can be created using the weak_ptr class.
4. What is a weak_ptr in C++ and when should it be used?
std::weak_ptr is a smart pointer that holds a non-owning reference to an object managed by std::shared_ptr. It does not participate in reference counting — holding a weak_ptr does not keep the object alive.
Why it exists:
shared_ptr uses reference counting to determine when to delete its managed object. If two objects hold shared_ptr references to each other, the counts never reach zero and the objects leak. weak_ptr breaks this cycle by observing the object without incrementing the strong count.
Typical use cases:
- Breaking cyclic
shared_ptrreferences — the classic use. Make one direction of the cycle aweak_ptr. - Caches and registries — the cache holds
weak_ptr; the cached object is freed normally when no other code holds ashared_ptrto it. - Observer/callback registries — an observable can store
weak_ptrto observers; if an observer is destroyed, theweak_ptrsafely reflects that rather than holding the observer alive. - Parent pointers in tree structures — children hold
shared_ptrto siblings/children; parents hold aweak_ptrback-reference.
Usage pattern — must lock() before use:
#include
auto shared = std::make_shared();
std::weak_ptr weak = shared;
// Later, possibly on a different thread:
if (auto locked = weak.lock()) { // returns shared_ptr; null if object was deleted
locked->doSomething();
} else {
// object has been destroyed
}
lock() is an atomic operation: it either succeeds (returning a valid shared_ptr that keeps the object alive for the duration of your use) or returns null — there is no race window between checking and using.
Other useful members:
weak.expired() // true if the managed object has been destroyed
weak.use_count() // current strong reference count (for diagnostics only)
weak.reset() // release the weak reference
Key interview point: you cannot dereference a weak_ptr directly. You must call lock() to obtain a temporary shared_ptr. This enforces the invariant that you can only access the object if it is still alive.
Follow-up 1
How does weak_ptr help in handling cyclic references?
Cyclic references occur when two or more objects have shared ownership of each other through shared_ptr instances. This can lead to memory leaks because the reference count of the objects never reaches zero. By using weak_ptr, one of the objects can hold a weak reference to the other object without contributing to its reference count. This breaks the cyclic reference and allows the objects to be destroyed when they are no longer needed.
Follow-up 2
Can you share a code snippet demonstrating the use of weak_ptr?
Certainly! Here's an example that demonstrates the use of weak_ptr:
#include
#include
class MyClass;
class MyOtherClass {
public:
std::weak_ptr myClassPtr;
};
class MyClass {
public:
std::shared_ptr myOtherClassPtr;
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};
int main() {
std::shared_ptr myClassPtr = std::make_shared();
std::shared_ptr myOtherClassPtr = std::make_shared();
myClassPtr->myOtherClassPtr = myOtherClassPtr;
myOtherClassPtr->myClassPtr = myClassPtr;
return 0;
}
In this example, MyClass and MyOtherClass have a cyclic reference through shared_ptr. By using weak_ptr for one of the references, the cyclic reference is broken and both objects can be safely destroyed.
Follow-up 3
What happens when you try to access an object through a weak_ptr?
When you try to access an object through a weak_ptr, you need to first check if the weak_ptr is still valid by calling the lock() function. This function returns a shared_ptr that shares ownership of the object if it is still alive, or an empty shared_ptr if the object has been destroyed. If the weak_ptr is valid, you can access the object using the shared_ptr returned by lock(). If the weak_ptr is invalid, attempting to access the object will result in undefined behavior.
Follow-up 4
How can you convert a weak_ptr to a shared_ptr?
To convert a weak_ptr to a shared_ptr, you can use the lock() function. The lock() function returns a shared_ptr that shares ownership of the object if it is still alive, or an empty shared_ptr if the object has been destroyed. Here's an example:
#include
#include
class MyClass {
public:
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};
int main() {
std::weak_ptr weakPtr;
{
std::shared_ptr sharedPtr = std::make_shared();
weakPtr = sharedPtr;
std::shared_ptr convertedSharedPtr = weakPtr.lock();
if (convertedSharedPtr) {
// Access the object through convertedSharedPtr
}
}
std::shared_ptr convertedSharedPtr = weakPtr.lock();
if (!convertedSharedPtr) {
std::cout << "Object has been destroyed" << std::endl;
}
return 0;
}
In this example, weakPtr is initially assigned a shared_ptr and then converted to a shared_ptr using lock(). If the object is still alive, convertedSharedPtr will be valid and you can access the object through it. If the object has been destroyed, convertedSharedPtr will be empty.
5. What are the advantages and disadvantages of using smart pointers in C++?
Smart pointers in C++ provide significant advantages over raw owning pointers, though they come with trade-offs worth understanding for an interview.
Advantages:
Automatic memory management. The owned object is destroyed when the smart pointer goes out of scope, including during stack unwinding from exceptions. This eliminates the most common source of memory leaks.
Enforced ownership semantics.
unique_ptrmakes sole ownership explicit and enforceable at compile time (copy is deleted; only move is allowed).shared_ptrtracks shared ownership via reference count. This makes code intent clear and prevents accidental double-delete.Exception safety. Code that mixes
newwith business logic is prone to leaking on exception paths. Smart pointers make the cleanup automatic regardless of how a scope exits.Rule of Zero enablement. A class whose members are all smart pointers and standard containers needs no user-defined destructor, copy constructor, or copy-assignment — the compiler generates correct versions, reducing boilerplate and bugs.
unique_ptrhas zero runtime overhead. With a default deleter it is as efficient as a raw pointer — the compiler optimizes away the abstraction entirely.
Disadvantages:
shared_ptrreference-count overhead. Each copy or destruction requires an atomic increment or decrement. In tight loops with manyshared_ptrcopies this can become measurable.make_sharedmitigates part of this by combining the object and control block in one allocation.Cyclic references with
shared_ptr. Two objects holdingshared_ptrto each other will never be freed. Requires disciplined use ofweak_ptrto break cycles — an easy mistake to miss.unique_ptris move-only. APIs designed around copyable raw pointers need redesigning to acceptunique_ptrby move or by reference (const unique_ptr&orT*for non-owning access via.get()).shared_ptrdoes not protect the pointed-to object. The reference count is thread-safe; the object itself is not. Developers sometimes assumeshared_ptrprovides full thread safety on the managed data, which it does not.Slight learning curve. Developers new to C++ ownership semantics need to understand when to use each type, when to use
weak_ptr, and what.get()and.release()mean.
Bottom line: in modern C++, smart pointers are the default. Reach for unique_ptr first, shared_ptr only when ownership is genuinely shared, and weak_ptr to break cycles or implement caches. Raw owning pointers belong only in low-level library internals or custom allocators.
Follow-up 1
Can smart pointers completely prevent memory leaks?
Yes, smart pointers can help prevent memory leaks in C++. Smart pointers, such as std::unique_ptr and std::shared_ptr, automatically manage the lifetime of dynamically allocated objects. When a smart pointer goes out of scope or is reset, it automatically releases the memory it owns, preventing memory leaks.
However, it's important to note that while smart pointers can greatly reduce the risk of memory leaks, they are not a silver bullet. It is still possible to create circular references or misuse smart pointers in a way that can lead to memory leaks. Therefore, it's important to use smart pointers correctly and be aware of potential pitfalls.
Follow-up 2
What is the overhead associated with using smart pointers?
Using smart pointers in C++ introduces some overhead compared to raw pointers. This overhead includes:
Memory overhead: Smart pointers typically require additional memory to store the control block, which holds information such as the reference count and the deleter function. This can increase the memory usage compared to raw pointers.
Performance overhead: Smart pointers may have some performance overhead compared to raw pointers due to the additional operations they perform, such as reference counting or checking for null pointers. However, in most cases, this overhead is negligible and does not significantly impact performance.
It's important to note that the overhead associated with using smart pointers is usually outweighed by the benefits they provide, such as automatic memory management and increased safety. However, in certain performance-critical scenarios, where every CPU cycle or byte of memory matters, using raw pointers may be more suitable.
Follow-up 3
Are there any scenarios where traditional pointers are more suitable than smart pointers?
Yes, there are some scenarios where traditional pointers (raw pointers) may be more suitable than smart pointers in C++:
Performance-critical scenarios: In certain performance-critical scenarios, where every CPU cycle or byte of memory matters, using raw pointers may be more efficient than using smart pointers. Smart pointers introduce some overhead in terms of memory usage and performance, which may be unacceptable in such scenarios.
Interfacing with legacy code: When interfacing with legacy code or external libraries that expect raw pointers, using smart pointers may not be feasible. In such cases, using raw pointers is necessary to maintain compatibility.
Fine-grained control over memory management: Smart pointers provide automatic memory management, which may not be desirable in certain situations where fine-grained control over memory allocation and deallocation is required. Raw pointers allow for more flexibility in managing memory manually.
It's important to carefully consider the specific requirements of the application and the trade-offs between using smart pointers and raw pointers before making a decision.
Follow-up 4
How do smart pointers affect performance in C++?
Smart pointers in C++ can have some impact on performance compared to raw pointers. The performance impact of using smart pointers includes:
Memory overhead: Smart pointers typically require additional memory to store the control block, which can increase the memory usage compared to raw pointers. This can have an impact on the overall memory footprint of the application.
Indirection: Smart pointers introduce an additional level of indirection compared to raw pointers. This means that accessing the underlying object through a smart pointer may require an extra level of dereferencing, which can have a small impact on performance.
Reference counting: Some smart pointers, such as
std::shared_ptr, use reference counting to manage the lifetime of dynamically allocated objects. This involves incrementing and decrementing a reference count, which can have a small performance impact compared to raw pointers.
However, it's important to note that the performance impact of using smart pointers is usually negligible and does not significantly affect the overall performance of the application. In most cases, the benefits of using smart pointers, such as automatic memory management and increased safety, outweigh the slight performance overhead.
Live mock interview
Mock interview: Smart Pointers in C++
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.