For educational purposes I implemented standard library smart pointers like class templates. They are definitely not a full replacement for the library classes but I'd like to get some comments on the way I use templates and how I can do better. I'd especially like to get criticism about the code clarity and readability. Performance is not a big concern here but comments regarding poor design choices regarding it are welcome.
I have 3 classes:
Smart_pointer_base
is an abstract base class for the other two.Shrd_ptr
is likestd::shared_ptr
Unq_ptr
is likestd::unique_ptr
Those are in separate header files, and each has a .tpp
file for definitions:
Smart_pointer_base.h
#ifndef BLOB_SMART_POINTER_BASE_H
#define BLOB_SMART_POINTER_BASE_H
// Superclass for Shrd_ptr and Unq_ptr
template <typename T>
class Smart_pointer_base {
public:
explicit Smart_pointer_base(T*managed_): managed(managed_) { }
Smart_pointer_base(): managed(nullptr) { }
virtual T&operator*();
virtual const T&operator*() const { return *managed; }
virtual T*operator->();
virtual const T*operator->() const { return managed; }
virtual ~Smart_pointer_base() = default;
protected:
/// `destruct` replaces the destructor
/// derived classes may call it to
/// deleted the `managed`
inline virtual void destruct(){ delete managed; }
// the pointer to the (hopefully) dynamically
// allocated memory
T* managed;
private:
};
// Definitions
#include "Smart_pointer_base.tpp"
#endif //BLOB_SMART_POINTER_BASE_H
Smart_pointer_base.tpp
#ifndef BLOB_SMART_POINTER_BASE_TPP
#define BLOB_SMART_POINTER_BASE_TPP
// Use the const version
template<typename T>
T &Smart_pointer_base<T>::operator*()
{
const auto& res = const_cast<const Smart_pointer_base*>(this)->operator*();
return const_cast<T&>(res);
}
// Use the const version
template<typename T>
T *Smart_pointer_base<T>::operator->()
{
const auto* res = const_cast<const Smart_pointer_base*>(this)->operator->();
return const_cast<T*>(res);
}
#endif // !BLOB_SMART_POINTER_BASE_TPP
Shrd_ptr.h
#ifndef BLOB_SHRD_PTR_H
#define BLOB_SHRD_PTR_H
#include <cstddef>
#include "Smart_pointer_base.h"
#include <functional>
#include <utility>
template <typename Y> const Y safe_increment(Y*);
template <typename Y> const Y safe_decrement(Y*);
template <typename T>
class Shrd_ptr: public Smart_pointer_base<T> {
typedef std::size_t size_type;
typedef std::function<void(const T*)> destructor_type;
public:
explicit Shrd_ptr(T* managed_, destructor_type destructor_ = nullptr)
: Smart_pointer_base<T>(managed_)
, user_count(new size_type(1))
, destructor(std::move(destructor_))
{ }
Shrd_ptr();
Shrd_ptr(const Shrd_ptr&);
Shrd_ptr&operator=(const Shrd_ptr&);
~Shrd_ptr() override;
void reset() noexcept { Shrd_ptr().swap(*this);}
template< class Y >
void reset( Y* ptr ) { Shrd_ptr<T>(ptr).swap(*this); }
template< class Y, class Deleter>
void reset( Y* ptr, Deleter d) { Shrd_ptr<T>(ptr, d).swap(*this); }
void swap(Shrd_ptr& r ) noexcept;
private:
void destruct() override;
void copy(const Shrd_ptr&);
destructor_type destructor;
mutable size_type* user_count;
// friends
friend const T safe_increment<T>(T*);
friend const T safe_decrement<T>(T*);
};
// Definitions
#include "Shrd_ptr.tpp"
#endif //BLOB_SHRD_PTR_H
Shrd_ptr.tpp
#ifndef BLOB_SHRD_PTR_TPP
#define BLOB_SHRD_PTR_TPP
template <typename T> class Smart_pointer_base;
template <typename T> class Shrd_ptr;
template<typename T>
Shrd_ptr<T>::~Shrd_ptr()
{
destruct();
}
template<typename T>
Shrd_ptr<T>::Shrd_ptr(const Shrd_ptr &rhs)
: Smart_pointer_base<T>(rhs.managed)
, user_count(rhs.user_count)
, destructor(rhs.destructor)
{
// increase user_count by one
// to denote added user
safe_increment(user_count);
}
template<typename T>
Shrd_ptr<T> &Shrd_ptr<T>::operator=(const Shrd_ptr &rhs)
{
// Check equality by comparing managed pointers
if (this->managed != rhs.managed) {
destruct();
copy(rhs);
}
return *this;
}
template<typename T>
void Shrd_ptr<T>::destruct()
{
// if count reaches zero
// No other pointer points
// to this managed
if (safe_decrement(user_count) == 0) {
delete user_count;
destructor ? destructor(this->managed) : Smart_pointer_base<T>::destruct();
}
}
template<typename T>
void Shrd_ptr<T>::copy(const Shrd_ptr &rhs)
{
// this function assumes that all
// necessary the destruction has been
// carried out
this->managed = rhs.managed;
safe_increment(rhs.user_count);
user_count = rhs.user_count;
destructor = rhs.destructor;
}
template<typename T>
Shrd_ptr<T>::Shrd_ptr()
: Smart_pointer_base<T>(nullptr)
, user_count(nullptr)
, destructor(nullptr)
{ }
template<typename T>
void Shrd_ptr<T>::swap(Shrd_ptr &r) noexcept
{
using std::swap;
swap(this->managed, r.managed);
swap(user_count, r.user_count);
swap(destructor, r.destructor);
}
// increment whatever `ptr` points to
// only if it is `ptr != nullptr`
// returns 0 if `ptr == nullptr`
template<typename T>
inline const T safe_increment(T *ptr)
{
if (ptr)
return ++*ptr;
return T();
}
// decrement whatever `ptr` points to
// only if it is `ptr != nullptr`
// returns 0 if `ptr == nullptr`
template<typename T>
inline const T safe_decrement(T *ptr)
{
if (ptr)
return --*ptr;
return T();
}
#endif // !BLOB_SHRD_PTR_TPP
Unq_ptr.h
#ifndef BLOB_UNQ_PTR_H
#define BLOB_UNQ_PTR_H
#include <functional>
#include "Smart_pointer_base.h"
template <typename T, typename destructor_type = std::function<void(const T*)>>
class Unq_ptr: public Smart_pointer_base<T> {
public:
explicit Unq_ptr(T*, destructor_type = [](const T*t){ delete t; });
Unq_ptr(Unq_ptr&&) noexcept;
Unq_ptr&operator=(Unq_ptr&&) noexcept;
Unq_ptr(const Unq_ptr&) = delete;
Unq_ptr&operator=(const Unq_ptr&) = delete;
~Unq_ptr() override;
private:
destructor_type destructor;
void move(Unq_ptr&&) noexcept;
void destruct() override;
};
// Definitions
#include "Unq_ptr.tpp"
#endif //BLOB_UNQ_PTR_H
Unq_ptr.tpp
#ifndef BLOB_UNQ_PTR_TPP
#define BLOB_UNQ_PTR_TPP
#include "Unq_ptr.h"
template<typename T, typename destructor_type> class Unq_ptr;
template<typename T, typename destructor_type>
Unq_ptr<T, destructor_type>::Unq_ptr(T * managed_, destructor_type destructor_)
: Smart_pointer_base<T>(managed_)
, destructor(destructor_)
{ }
template<typename T, typename destructor_type>
void Unq_ptr<T, destructor_type>::move(Unq_ptr &&rhs) noexcept
{
this->managed = rhs.managed;
rhs.managed = nullptr;
// No need to set `rhs.destructor` to `nullptr`
destructor = rhs.destructor;
}
template<typename T, typename destructor_type>
void Unq_ptr<T, destructor_type>::destruct()
{
destructor(this->managed);
}
template<typename T, typename destructor_type>
Unq_ptr<T, destructor_type>::Unq_ptr(Unq_ptr &&rhs) noexcept
{
move(rhs);
destruct();
}
template<typename T, typename destructor_type>
Unq_ptr<T, destructor_type> &Unq_ptr<T, destructor_type>::operator=(Unq_ptr &&rhs) noexcept
{
// No need to check for self assignment
// since we only take an r-value assignment
move(rhs);
destructor();
return *this;
}
template<typename T, typename destructor_type>
Unq_ptr<T, destructor_type>::~Unq_ptr()
{
destruct();
}
#endif // !BLOB_UNQ_PTR_TPP
Besides those, I have a source file to test those headers:
test.cpp
#include <vector>
#include <iostream>
#include "Smart_pointer_base.h"
#include "Shrd_ptr.h"
#include "Unq_ptr.h"
#include <functional>
using std::vector;
using std::cout;
using std::endl;
using std::function;
using ivec = vector<int>;
// these `get_shared` and `get_unique` functions are
// used only to get smart pointers to dynamically
// allocated objects
Shrd_ptr<ivec> get_shared()
{ return Shrd_ptr<ivec>(new ivec{1, 3, 5, 7, 9}, [](const ivec*p){ delete p; cout << "Shared Deleted!\n";}); }
Unq_ptr<ivec> get_unique()
{
return Unq_ptr<ivec, function<void(const ivec*)>>
(new ivec{0, 2, 4, 6, 8}, [](const ivec*p){ delete p; cout << "Unique Deleted!\n"; } );
}
int main()
{
auto e_shr = get_shared();
auto b_shr = e_shr;
e_shr.reset();
auto s_shr = b_shr;
b_shr.reset();
for (const auto& elm : *s_shr) {
cout << elm << endl;
}
auto e_unq = get_unique();
for (const auto& elm : *e_unq) {
cout << elm << endl;
}
return 0;
}