Custom Unique Pointer Wrapper
Guided Challenges and Capstone

10.1 Custom Unique Pointer Wrapper

This challenge distills Lessons 4.3 (move semantics), 4.2 (RAII), and 7.1 (std::unique_ptr) into one small type you implement yourself. Production code should use std::unique_ptr, it supports arrays, custom deleters, conversions, and years of edge-case fixes. Here you learn the core invariant: exactly one owner releases the heap object.

Why exclusive ownership exists

If two owners both call delete on the same address, you get double free (heap corruption). If nobody calls delete, you leak. Exclusive ownership makes the rule mechanical: one wrapper, one delete in one destructor.

Move semantics review

Copying is deleted because a copy would duplicate responsibility. Move transfers the raw pointer from source to destination and sets the source to nullptr. After auto b = std::move(a);, a must not delete the object, only b may.

Mark move operations noexcept when they cannot throw. Standard containers rely on that guarantee when they reallocate and move elements, Lesson 8.4 discussed vector reallocation and move optimizations.

Move assignment algorithm

Move assignment must handle self-move safely (if (this != &other)), destroy any resource already owned, steal the pointer, and null the source. Forgetting to delete the existing resource before stealing causes leaks; forgetting to null the source causes double delete.

1. if (this == &other) return *this;
// 2. delete rawPtr;            // release current ownership
// 3. rawPtr = other.rawPtr; other.rawPtr = nullptr;
Move ownership in three steps (pseudocode).

What production unique_ptr adds

* std::make_unique<T>(args...) for exception-safe allocation.

* unique_ptr<T[]> and delete[] for arrays.

Custom deleters for FILE, sockets, and foreign handles.

* Conversion from unique_ptr<Derived> to unique_ptr<Base> when safe.

The Challenge: Exclusive Ownership & RAII

Your UniquePtr<T> must guarantee:

1. Exclusive ownership: copying is deleted.

2. RAII: destructor runs delete on the owned pointer.

3. Move transfer: move constructor and move assignment steal and nullify.

Open the template in the playground. Complete the // TODO sections so every assertion passes.

#include <iostream>
#include <cassert>
#include <utility>

template <typename T>
class UniquePtr {
private:
    T* rawPtr;
public:
    explicit UniquePtr(T* p = nullptr) : rawPtr{p} {}

    ~UniquePtr() {
        delete rawPtr;
    }

    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;

    // TODO: Implement move constructor (transfer ownership, nullify source)
    UniquePtr(UniquePtr&& other) noexcept {
        // Your code here
    }

    // TODO: Implement move assignment (release existing resource, transfer, nullify source)
    UniquePtr& operator=(UniquePtr&& other) noexcept {
        // Your code here
        return *this;
    }

    T& operator*() const { return *rawPtr; }
    T* operator->() const { return rawPtr; }
    T* get() const { return rawPtr; }
    explicit operator bool() const { return rawPtr != nullptr; }
};

struct Resource {
    int val;
    Resource(int v) : val{v} {}
};

int main() {
    UniquePtr<Resource> p1{new Resource{10}};
    assert(p1.get() != nullptr);
    assert(p1->val == 10);
    assert((*p1).val == 10);

    UniquePtr<Resource> p2{std::move(p1)};
    assert(!p1);
    assert(p2.get() != nullptr);
    assert(p2->val == 10);

    UniquePtr<Resource> p3{new Resource{20}};
    p3 = std::move(p2);
    assert(!p2);
    assert(p3->val == 10);

    std::cout << "All UniquePtr assertions passed successfully!\n";
    return 0;
}
UniquePtr template capstone challenge code.

If you get stuck, expand the reference solution below.

Reference Solution: Custom UniquePtr wrapper
Key solution features:
// - Copying is explicitly blocked using '= delete'
// - Move operations steal raw pointers and nullify the original sources
// - Destructor cleanly executes delete on the internal pointer

template <typename T>
class UniquePtr {
private:
    T* rawPtr;
public:
    explicit UniquePtr(T* p = nullptr) : rawPtr{p} {}
    ~UniquePtr() { delete rawPtr; }

    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;

    UniquePtr(UniquePtr&& other) noexcept : rawPtr{other.rawPtr} {
        other.rawPtr = nullptr;
    }

    UniquePtr& operator=(UniquePtr&& other) noexcept {
        if (this != &other) {
            delete rawPtr;
            rawPtr = other.rawPtr;
            other.rawPtr = nullptr;
        }
        return *this;
    }

    T& operator*() const { return *rawPtr; }
    T* operator->() const { return rawPtr; }
    T* get() const { return rawPtr; }
    explicit operator bool() const { return rawPtr != nullptr; }
};
Reference Solution: Custom UniquePtr wrapper.
Finished reading this lesson?