Introduction
Smart pointers are a fancy wrapper over raw pointers. Smart pointers are RAII compliant. RAII(Resource Acquisition Is Initialization) is a design paradigm and its a concept very emblematic in C++. It’s a simple idea - all resources used by a class should be acquired in the constructor. All resources used by a class must be released in the destructor! It turns out that by abiding by the RAII policy, we avoid half-valid states.
The std::unique_ptr
in the <memory>
header is a smart pointer that excusively owns and manages the objects created dynamically on the heap through an underlying raw pointer. It subsequently disposes off the object, when it goes out of scope and is destroyed.
struct X{};
std::unique_ptr<X> p1 = std::make_unique<X>();
//std::unique_ptr<X> p2(p1); // Error when calling copy constructor,
// p1 is the exclusive owner
std::unique_ptr
enforces exclusive ownership using the fact, that it is not copy-constructible or copy-assignable. Note however, that the below code compiles successfully and is valid code.
int* p = new int(10);
std::unique_ptr<int> p1(p);
std::unique_ptr<int> p2(p);
The copy constructor and the copy assignment operator of std::unique_ptr<T>
are marked delete
. It is however, move constructible and move-assignable.
Basic functionalities to expect out of std::unique_ptr<T>
I skimmed through the documentation for std::unique_ptr
on cppreference.com. A basic implementation of unique_ptr
in less than 200 lines of code should pass the following unit tests:
#include "pch.h"
#include "gtest/gtest.h"
#include "../UniquePointer/UniquePointer.h"
(ComparisonOperator, Test) {
TEST/* Non member function operator==
Comparison with another unique_ptr or nullptr */
::unique_ptr<int> ptr1(new int(10));
dev::unique_ptr<int> ptr2(new int(10));
dev(ptr1 == ptr1, true);
EXPECT_EQ(ptr1 != ptr2, true);
EXPECT_EQ}
(CreateAndAccess, Test) {
TEST/* Create and access */
::unique_ptr<int> ptr(new int(10));
dev(ptr != nullptr, true);
EXPECT_EQ(*ptr == 10, true);
EXPECT_EQ
}
(ResetUniquePtr, Test) {
TEST/* Reset unique_ptr
Replaces the managed object with a new one*/
::unique_ptr<int> ptr(new int(10));
dev.reset(new int(20));
ptr(ptr != nullptr, true);
EXPECT_EQ(*ptr == 20, true);
EXPECT_EQ
// Self-reset test
.reset(ptr.get());
ptr}
(ReleaseUniquePtr, Test) {
TEST/* Release unique_ptr ownership -
Returns a pointer to the managed object and releases ownership */
::unique_ptr<double> ptr(new double(3.14));
devdouble* rawPtr = ptr.release();
(ptr == nullptr, true);
EXPECT_EQ(rawPtr != nullptr, true);
EXPECT_EQ(*rawPtr == 3.14, true);
EXPECT_EQ
}
(SwapFunction, Test) {
TEST/* Non-member function swap
Swap the managed objects */
::unique_ptr<int> ptr1(new int(10));
dev::unique_ptr<int> ptr2(new int(20));
dev(ptr1, ptr2);
swap(*ptr1 == 20, true);
EXPECT_EQ(*ptr2 == 10, true);
EXPECT_EQ
}
(GetRawUnderlyingPtr, Test) {
TEST/* Get the raw underlying pointer */
int* x = new int(10);
::unique_ptr<int> ptr(x);
devint* rawPtr = ptr.get();
(*rawPtr == 10, true);
EXPECT_EQ(rawPtr == x, true);
EXPECT_EQ
}
(BoolOperator, Test) {
TEST/* operator bool to test if the unique pointer owns an object */
::unique_ptr<int> ptr(new int(42));
dev(bool(ptr), true);
EXPECT_EQ.reset(nullptr);
ptr(bool(!ptr), true);
EXPECT_EQ
}
(IndirectionOperator, Test) {
TEST/* indirection operator* to dereference pointer to managed object,
member access operator -> to call member function*/
struct X {
int n;
int foo() { return n; }
};
::unique_ptr<X> ptr(new X(10));
dev((*ptr).n == 10, true);
EXPECT_EQ(ptr->foo() == 10, true);
EXPECT_EQ
}
(MakeUnique, Test) {
TEST/* Constructs an object of type T and wraps it in a unique_ptr.*/
struct Point3D {
double x, y, z;
() {}
Point3D
(double x_, double y_, double z_) : x(x_), y(y_), z(z_) {}
Point3D
(const Point3D& other) : x(other.x), y(other.y), z(other.z) {}
Point3D
(Point3D&& other)
Point3D: x(std::move(other.x))
, y(std::move(other.y))
, z(std::move(other.z))
{
}
};
// Use the default constructor.
::unique_ptr<Point3D> v1 = dev::make_unique<Point3D>();
dev// Use the constructor that matches these arguments.
::unique_ptr<Point3D> v2 = dev::make_unique<Point3D>(0, 1, 2);
dev// Create a unique_ptr to a vector of 5 elements.
//dev::unique_ptr<std::vector<int>> v3 = dev::make_unique<std::vector<int>>({1,2,3,4,5});
}
A custom implementation
// UniquePointer.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include <iostream>
#include <memory>
#include <cassert>
#include <utility>
#include <vector>
#include <optional>
namespace dev {
// Implement unique_ptr here
template<typename T>
class unique_ptr {
public:
// Default c'tor
() : ptr{ nullptr } {}
unique_ptr
// Copy c'tor should be deleted to enforce the concept that this is
// an owning pointer
(const unique_ptr& u) = delete;
unique_ptr
// Copy assignment should also be deleted to enforce the ownership of the
// managed object
& operator=(const unique_ptr&) = delete;
unique_ptr
// Move constructor
(unique_ptr&& other) : ptr{ nullptr }
unique_ptr{
std::swap(ptr, other.ptr);
}
// Move assignment operator
& operator=(unique_ptr&& other) {
unique_ptrif (ptr == other)
return (*this);
();
delete_underlying_ptr= std::exchange(other.ptr, nullptr);
ptr return (*this);
}
// Parameterized construtor
(T* p)
unique_ptr: ptr{ p }
{
}
// Overload deferencing operator *
& operator*() {
Treturn (*ptr);
}
// Overload deferencing operator *
& operator*() const {
Treturn (*ptr);
}
// get the raw pointer
* get() const
T{
return ptr;
}
/* Reset the unique_ptr */
void reset(T* other) {
if (ptr == other)
return;
();
delete_underlying_ptrstd::swap(ptr, other);
}
/* Release unique_ptr ownership */
* release() {
Treturn std::exchange(ptr, nullptr);
}
/* Destructor */
~unique_ptr() {
();
delete_underlying_ptr}
/* swap - Exchange the contents of ptr1 and ptr2 member-by-member */
friend void swap(dev::unique_ptr<T>& ptr1, dev::unique_ptr<T>& ptr2) noexcept
{
//Re-wire the raw pointers
std::swap(ptr1.ptr, ptr2.ptr);
}
/* operator bool */
constexpr operator bool() {
return (ptr != nullptr);
}
/* Member access operator - return the underlying pointer */
* operator->() {
Treturn ptr;
}
/* Helper function to invoke delete on the underlying raw pointer */
void delete_underlying_ptr() {
if (ptr != nullptr) {
delete ptr;
= nullptr;
ptr }
}
private:
* ptr;
T};
template<typename T, typename... Args>
::unique_ptr<T> make_unique(Args&&... args) {
devreturn dev::unique_ptr(new T(std::forward<Args>(args)...));
}
/* Non-member functions */
//Overload operator==
template<typename T1, typename T2>
bool operator==(const dev::unique_ptr<T1>& lhs, const dev::unique_ptr<T2>& rhs) {
return lhs.get() == rhs.get();
}
/*template<typename T1> */
template<typename T1>
bool operator==(const dev::unique_ptr<T1>& lhs, std::nullptr_t rhs) {
return lhs.get() == nullptr;
}
template<typename T1>
bool operator==(std::nullptr_t lhs, const dev::unique_ptr<T1>& rhs) {
return rhs.get() == nullptr;
}
//Overload operator!=
template<typename T1, typename T2>
bool operator!=(const dev::unique_ptr<T1>& lhs, const dev::unique_ptr<T2>& rhs) {
return !(lhs == rhs);
}
template<typename T1>
bool operator!=(const dev::unique_ptr<T1>& lhs, std::nullopt_t& rhs) {
return !(lhs == rhs);
}
template<typename T1>
bool operator!=(std::nullopt_t& lhs, const dev::unique_ptr<T1>& rhs) {
return !(lhs == rhs);
}
}