All Notes
Implementing STL's std::shared_ptr
my own implementation of shared ptr and it’s corresponding weak ptrs
not ready to do a write up on it. i want to see other people’s implementation - i think control block doesn’t need to be its own component in a different file - just define it in sharedptr class and make it a friend?
shared ptr
#pragma once
#include <utility>
#include <memory>
#include <atomic>
#include "utils/ControlBlock.h"
template<typename T, typename Deleter = std::default_delete<T>>
class SharedPtr {
public:
/* Constructors */
SharedPtr() noexcept = default;
SharedPtr(T* ptr) noexcept : ptr_(ptr) {
if (ptr) {
control_block_ = new ControlBlock<T, Deleter>(Deleter{});
} else {
control_block_ = nullptr;
}
}
SharedPtr(T* ptr, Deleter deleter) noexcept : ptr_(ptr) {
if (ptr) {
control_block_ = new ControlBlock<T, Deleter>(deleter);
} else {
control_block_ = nullptr;
}
}
SharedPtr(const SharedPtr& other) noexcept : ptr_(other.ptr_), control_block_(other.control_block_) {
if (control_block_) {
control_block_->ref_count_.fetch_add(1);
}
}
SharedPtr(SharedPtr&& other) noexcept :
ptr_(std::exchange(other.ptr_, nullptr)),
control_block_(std::exchange(other.control_block_, nullptr)) {}
/* Destructor */
~SharedPtr() noexcept {
if (control_block_ && control_block_->ref_count_.fetch_sub(1) == 1) {
control_block_->deleter_(ptr_);
if (control_block_->weak_count_.load() == 0) delete control_block_;
}
}
/* Assignment operators */
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
if (control_block_ && control_block_->ref_count_.fetch_sub(1) == 1) {
control_block_->deleter_(ptr_);
delete control_block_;
}
ptr_ = std::exchange(other.ptr_, nullptr);
control_block_ = std::exchange(other.control_block_, nullptr);
}
return *this;
}
SharedPtr& operator=(const SharedPtr& other) noexcept {
if (this != &other) {
if (control_block_ && control_block_->ref_count_.fetch_sub(1) == 1) {
control_block_->deleter_(ptr_);
delete control_block_;
}
ptr_ = other.ptr_;
control_block_ = other.control_block_;
if (control_block_) control_block_->ref_count_++;
}
return *this;
}
T* get() const noexcept {
return ptr_;
}
T* operator->() const noexcept {
return ptr_;
}
T& operator*() const noexcept {
return *ptr_;
}
long use_count() const noexcept {
if (control_block_) {
return control_block_->ref_count_.load();
}
return 0;
}
bool unique() const noexcept {
return use_count() == 1;
}
void reset(T* ptr = nullptr) noexcept {
if (control_block_ && control_block_->ref_count_.fetch_sub(1) == 1) {
control_block_->deleter_(ptr_);
delete control_block_;
}
ptr_ = ptr;
if (ptr) {
control_block_ = new ControlBlock<T, Deleter>(Deleter{});
} else {
control_block_ = nullptr;
}
}
void swap(SharedPtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(control_block_, other.control_block_);
}
explicit operator bool() const noexcept {
return ptr_ != nullptr;
}
bool operator==(const SharedPtr& other) const noexcept {
return ptr_ == other.ptr_;
}
bool operator!=(const SharedPtr& 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);
}
private:
SharedPtr(T* ptr, ControlBlock<T, Deleter>* control_block) :
ptr_(ptr), control_block_(control_block) {}
// WeakPtr should be able to access the private ctor
// WeakPtr here is a forward declaration - no need to import module explicitly
template<typename U> friend class WeakPtr;
private:
T* ptr_ = nullptr;
ControlBlock<T, Deleter>* control_block_ = nullptr;
};
// TODO:
// make_shared exists but we're not doing the performance improvements
// to improve cache locality / reduce cache misses
template<typename T, typename... Arg>
SharedPtr<T> make_shared(Arg&&... args) {
return SharedPtr<T>(new T(std::forward<Arg>(args)...));
}
weak ptr
#pragma once
#include <utility>
#include "utils/ControlBlock.h"
#include "shared_ptr/SharedPtr.h"
namespace {
template<typename T>
void try_delete_control_block(ControlBlock<T>* control_block) {
if (control_block && control_block->weak_count_.fetch_sub(1) == 1) {
if (control_block->ref_count_.load() == 0) {
delete control_block;
}
}
}
}
template<typename T>
class WeakPtr {
public:
WeakPtr() = default;
WeakPtr(const SharedPtr<T>& shared_ptr) noexcept :
ptr_(shared_ptr.ptr_),
control_block_(shared_ptr.control_block_) {
if (control_block_) control_block_->weak_count_++;
}
WeakPtr(const WeakPtr& other) noexcept :
ptr_(other.ptr_),
control_block_(other.control_block_) {
if (control_block_) control_block_->weak_count_++;
}
WeakPtr(WeakPtr&& other) noexcept :
ptr_(std::exchange(other.ptr_, nullptr)),
control_block_(std::exchange(other.control_block_, nullptr)) {}
~WeakPtr() noexcept {
try_delete_control_block(control_block_);
}
// operator assign {copy, move}
WeakPtr& operator=(const WeakPtr& other) noexcept {
if (this != &other) {
try_delete_control_block(control_block_);
ptr_ = other.ptr_;
control_block_ = other.control_block_;
if (control_block_) control_block_->weak_count_++;
}
return *this;
}
WeakPtr& operator=(WeakPtr&& other) noexcept {
if (this != &other) {
try_delete_control_block(control_block_);
ptr_ = std::exchange(other.ptr_, nullptr);
control_block_ = std::exchange(other.control_block_, nullptr);
}
return *this;
}
bool expired() const noexcept {
if (!control_block_) return true;
return control_block_->ref_count_.load() == 0;
}
long use_count() const noexcept {
if (!control_block_) return 0;
return control_block_->ref_count_.load();
}
SharedPtr<T> lock() const noexcept {
if (!control_block_) {
return SharedPtr<T>();
}
auto ref_count = control_block_->ref_count_.load();
do {
if (ref_count == 0) {
return SharedPtr<T>();
}
} while(!control_block_->ref_count_.compare_exchange_weak(ref_count, ref_count + 1));
return SharedPtr<T>(ptr_, control_block_);
}
void reset() noexcept {
ptr_ = nullptr;
try_delete_control_block(control_block_);
control_block_ = nullptr;
}
void swap(WeakPtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(control_block_, other.control_block_);
}
private:
T* ptr_ = nullptr;
ControlBlock<T>* control_block_ = nullptr;
};
ctrl block
#pragma once
#include <memory>
template<typename T, typename Deleter = std::default_delete<T>>
struct ControlBlock {
ControlBlock(Deleter deleter) :
ref_count_(1),
weak_count_(0),
deleter_(deleter) {}
std::atomic<long> ref_count_;
std::atomic<long> weak_count_;
Deleter deleter_{};
};
Other notes about STL Implementations and/or C++
- 🌲Part 1. Thread Pools
Thread pooling with c++20 primitives
- 🌲Part 2. Work Stealing Thread Pools
Work Stealing Thread Poools C++20
- 🌿Developing a deep learning framework
Developing a deep learning framework
- 🌱MPMC Queue
MPMC Queue
- 🌱C++ low-latency design patterns
A brief description of what this note covers
- 🌱Atomics
Atomics
- 🌿SPSC Queue
SPSC Thread-Safe Queue
- 🌿Implementing STL's std::unique_ptr
Implementing STL's std::unique_ptr
- 🌿Implementing STL's std::vector
A brief description of what this note covers
- 🌿Type Erasure in C++
Type Erasure in C++