All Notes

Implementing STL's std::unique_ptr

Last updated

Oct 30, 2025

implementation probably okay by now. should probably look at doing write up. outline requirements, test cases, pseudocode, cpp code

impl

#pragma once

#include <utility>
#include <memory>
#include <type_traits>

template<typename T, typename Deleter = std::default_delete<T>>
class UniquePtr {
public:
UniquePtr() noexcept = default;
UniquePtr(T* ptr) noexcept : ptr_(ptr) {}
UniquePtr(T* ptr, Deleter deleter) noexcept : ptr_(ptr), deleter_(deleter) {}
UniquePtr(UniquePtr&& other) noexcept :
ptr_(std::exchange(other.ptr_, nullptr)),
deleter_(std::move(other.deleter_)) {}

~UniquePtr() {
if (ptr_) {
deleter_(ptr_);
}
}

UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {

if (ptr_) deleter_(ptr_);
ptr_ = std::exchange(other.ptr_, nullptr);
deleter_ = std::move(other.deleter_);
}
return *this;
}

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

T* get() const noexcept {
return ptr_;
}

T* release() noexcept {
T* temp = ptr_;
ptr_ = nullptr;
return temp;
}

void reset(T* ptr = nullptr) noexcept {
if (ptr_) deleter_(ptr_);
ptr_ = ptr;
}

void swap(UniquePtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(deleter_, other.deleter_);
}

T& operator*() const noexcept {
return *ptr_;
}

T* operator->() const noexcept {
return ptr_;
}

explicit operator bool() const noexcept {
return ptr_ != nullptr;
}

bool operator==(const UniquePtr& other) const noexcept {
return ptr_ == other.ptr_;
}

bool operator!=(const UniquePtr& other) const noexcept {
return !(ptr_ == other.ptr_);
}

bool operator==(std::nullptr_t) const noexcept {
return ptr_ == nullptr;
}

bool operator!=(std::nullptr_t) const noexcept {
return !(ptr_ == nullptr);
}

// what to delete? the copy ctor and assign op
private:
T* ptr_ = nullptr;
Deleter deleter_{};
};

tests

#include <type_traits>
#include <gtest/gtest.h>

#include "unique_ptr/UniquePtr.h"
/* static asserts */
// • Not copy constructible - Cannot create copies via constructor
static_assert(!std::is_copy_constructible<UniquePtr<int>>::value);
// • Not copy assignable - Cannot create copies via assignment operator
static_assert(!std::is_copy_assignable<UniquePtr<int>>::value);
// • Move constructible - Can be constructed from rvalue
static_assert(std::is_move_constructible<UniquePtr<int>>::value);
// • Move assignable - Can be assigned from rvalue
static_assert(std::is_move_assignable<UniquePtr<int>>::value);
// could probably check for other things
// e.g. noexceptions in certain methods etc.

class UniquePtrTest : public ::testing::Test {
protected:
struct TestObject {
static int count;
TestObject() {count++;}
~TestObject() {count--;}
};
};

int UniquePtrTest::TestObject::count = 0;

/* construction */
TEST_F(UniquePtrTest, DefaultConstruction) {
UniquePtr<int> ptr;
EXPECT_EQ(ptr.get(), nullptr);
EXPECT_FALSE(ptr);
}

TEST_F(UniquePtrTest, PointerConstruction) {
auto* raw = new int(42);
UniquePtr<int> ptr(raw);
EXPECT_EQ(ptr.get(), raw);
EXPECT_TRUE(ptr);
EXPECT_EQ(*ptr, 42);
}

/* Move semantics */
TEST_F(UniquePtrTest, MoveConstruction) {
auto* raw = new int(42);
UniquePtr<int> ptr1(raw);
UniquePtr<int> ptr2 = std::move(ptr1);

EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(ptr2.get(), raw);
EXPECT_EQ(*ptr2, 42);
}

TEST_F(UniquePtrTest, MoveAssignment) {
UniquePtr<int> ptr1(new int(42));
UniquePtr<int> ptr2(new int(100));

ptr2 = std::move(ptr1);

EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(*ptr2, 42);
}

/* memory management */
TEST_F(UniquePtrTest, Reset) {
TestObject::count = 0;
UniquePtr<TestObject> ptr(new TestObject());
EXPECT_EQ(TestObject::count, 1);

ptr.reset();
EXPECT_EQ(TestObject::count, 0);
EXPECT_EQ(ptr.get(), nullptr);
}

TEST_F(UniquePtrTest, Release) {
auto* raw = new TestObject();
UniquePtr<TestObject> ptr(raw);

auto* released = ptr.release();
EXPECT_EQ(released, raw);
EXPECT_EQ(ptr.get(), nullptr);

delete released; // Manual cleanup
}

Other notes about STL Implementations and/or C++