79. Unique Pointer Custom Deleter

In embedded and firmware-oriented C++ systems, objects are frequently created inside factory functions and manage multiple dynamically allocated resources internally. Such objects often cannot rely on a default destructor and instead must be cleaned up using a specific cleanup routine to ensure deterministic and correct resource release.

You are given:

  • A class that dynamically allocates two internal buffers
  • A factory function that creates the object dynamically
  • A custom cleanup function that must be used to destroy the object correctly

Your task is to use std::unique_ptr with a custom deleter so that:

  • The object is created inside a function
  • Ownership is transferred safely to the caller
  • Cleanup happens automatically when the object goes out of scope
  • The custom cleanup function is invoked exactly once
  • No manual calls to delete or cleanup() are made

Program Flow:

  1. Read integer N
  2. Call a factory function that dynamically allocates and returns an object
  3. Store the returned object in a std::unique_ptr with a custom deleter
  4. Read N integer values and store them in the object
  5. Print the stored values
  6. Exit the scope
  7. Custom cleanup must execute automatically

Input:

  • Integer N (1 ≤ N ≤ 100)
  • N space-separated integers on the next line

Notes:

  • Values are stored in uint8_t buffers
    (values outside the range 0–255 are truncated modulo 256)
  • Object allocation must occur inside the factory function
  • Do not manually call delete or cleanup()

Output:

  • One line containing the stored values separated by spaces
  • A newline
  • The exact text:

    Object cleaned
    

 

Example Input:

3
10 20 30 

Example Output:

10 20 30
Object cleaned

 

Constraints:

  • 1 ≤ N ≤ 100
  • The object owns multiple dynamically allocated members
  • Cleanup must occur through the provided cleanup function
  • Output format must match exactly

 

 

 

 

Need Help? Refer to the Quick Guide below

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.

Types & Usage

1. std::unique_ptr (The Embedded Standard)

Represents exclusive ownership. Only one pointer can own the resource.

  • Copying: Banned (Compiler error).
  • Moving: Allowed (Transfers ownership).
  • Overhead: Zero. It is exactly the same size as a raw pointer.
#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.

  • It maintains a Reference Count. Every time you copy the pointer, count +1. When a pointer dies, count -1.
  • The memory is freed only when the count hits 0.
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.

Memory Layout & Overhead

Featureunique_ptrshared_ptr
OwnershipSolo (1 owner).Shared (N owners).
Sizesizeof(void*) (4 bytes).2 * sizeof(void*) (Ptr + Control Block).
PerformanceFast (Inline calls).Slower (Atomic Ref-Counting).
Embedded UseRecommended (99% of cases).Avoid (unless necessary).

Relevance in Embedded/Firmware

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.

Common Pitfalls (Practical Tips)

PitfallDetails
shared_ptr Overheadshared_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_ptrDeprecated and removed in C++17. Never use it. It had broken copy semantics. Use unique_ptr instead.
get() misuseptr.get() returns the raw pointer. Be careful not to manually delete this raw pointer, or the smart pointer will double-free it later.
make_uniqueAlways use std::make_unique<T>() instead of new T(). It’s safer (prevents leaks if constructor throws) and cleaner.