You are given a program that allocates a dynamic buffer inside a limited scope.
Your task is to manage this buffer using std::unique_ptr so that the memory is released automatically when the scope ends, without calling delete.
This problem introduces the core idea of RAII (Resource Acquisition Is Initialization), where resource lifetime is tied to object lifetime.
Program Flow:
NN integersN integers into the buffer
Example Input:
4
10 20 30 40
Example Output:
10 20 30 40
Scope ended
Constraints:
N ranges from 1 to 100std::unique_ptr for ownershipdelete explicitly
Raw pointers (T* ptr = new T()) impose a heavy burden: you must manually call delete, otherwise you get memory leaks. If you delete too early, you get dangling pointer crashes.
Smart Pointers are wrapper classes that own the raw pointer. They automatically call delete (or a custom cleanup function) when the pointer goes out of scope, using the RAII pattern.
They live in the <memory> header.
1. std::unique_ptr (The Embedded Standard)
Represents exclusive ownership. Only one pointer can own the resource.
#include <memory>
void setup_sensor() {
// Create unique pointer (Preferred syntax: make_unique)
std::unique_ptr<Sensor> s = std::make_unique<Sensor>(10);
s->init(); // Use -> just like a raw pointer
// No delete needed!
// When function returns, 's' is destroyed -> calls ~Sensor() -> frees memory.
}2. std::shared_ptr (Reference Counted)
Represents shared ownership. Multiple pointers can point to the same object.
+1. When a pointer dies, count -1.std::shared_ptr<Data> p1 = std::make_shared<Data>();
{
std::shared_ptr<Data> p2 = p1; // Count = 2
} // p2 dies. Count = 1. Data still exists.
// p1 dies. Count = 0. Data deleted.3. std::weak_ptr
A non-owning observer of a shared_ptr. It doesn't increase the reference count. Used to break Circular Dependencies (A points to B, B points to A) which cause memory leaks.
| Feature | unique_ptr | shared_ptr |
|---|---|---|
| Ownership | Solo (1 owner). | Shared (N owners). |
| Size | sizeof(void*) (4 bytes). | 2 * sizeof(void*) (Ptr + Control Block). |
| Performance | Fast (Inline calls). | Slower (Atomic Ref-Counting). |
| Embedded Use | Recommended (99% of cases). | Avoid (unless necessary). |
1. unique_ptr for Drivers
Hardware drivers usually have single ownership. A UART object manages a specific hardware block.
Using unique_ptr<UART> ensures that if the driver is replaced or shut down, the cleanup (destructor) happens automatically.
2. Custom Deleters (No Heap Required)
Smart pointers are often associated with new/delete (Heap), but they can manage any resource. You can teach a unique_ptr to call a specific function (like fclose or free_buffer) instead of delete.
// A pointer that calls 'close_file' instead of 'delete'
auto deleter = [](FILE* f) { fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> file_ptr(fopen("log.txt", "w"), deleter);
// When file_ptr goes out of scope, fclose() is called automatically.3. Factory Patterns
Factories that return std::unique_ptr<Base> allow you to return different derived driver types safely without worrying about who is responsible for deleting them.
| Pitfall | Details |
|---|---|
❌ shared_ptr Overhead | shared_ptr allocates a "Control Block" on the heap to store the counter. This causes Heap Fragmentation and involves atomic instructions (slow) to update the count. Avoid in tight loops. |
❌ auto_ptr | Deprecated and removed in C++17. Never use it. It had broken copy semantics. Use unique_ptr instead. |
❌ get() misuse | ptr.get() returns the raw pointer. Be careful not to manually delete this raw pointer, or the smart pointer will double-free it later. |
✅ make_unique | Always use std::make_unique<T>() instead of new T(). It’s safer (prevents leaks if constructor throws) and cleaner. |