I've written an implementation of shared_ptr. It doesn't support weak_ptr (yet) but below is the code. I'd appreciate any feedback, comments.
#pragma once
#include <iostream>
#include <memory>
#include <utility>
#include <cxxabi.h>
template <typename T>
void del(T* t)
{
delete t;
};
namespace graham
{
using namespace std;
struct control_block_base
{
template <typename T, typename Deleter>
control_block_base(T* t, const Deleter& d)
{
_cleanup = [=] ()
{
const Deleter& _d= std::move(d);
_d(t);
// TODO only delete this is weak_ptr count == 0
delete this;
};
_deleter = (void*) &d;
// only for debugging/operator<<
int status_ignored;
_true_type = abi::__cxa_demangle(typeid(T).name(),NULL,NULL,&status_ignored);
};
virtual ~control_block_base()
{
}
void reset()
{
dec();
}
void dec()
{
if (!--_ref_count)
{
_cleanup();
}
}
void* get_deleter()
{
return _deleter;
};
std::string _true_type;
std::atomic<int> _ref_count;
void* _deleter;
private:
function<void()> _cleanup;
};
template <typename T>
struct control_block : public control_block_base
{
control_block(nullptr_t ) : _ptr{nullptr}
{
this->_ref_count = 0;
}
control_block(T* ptr) : control_block_base(ptr, std::default_delete<T>()) ,
_ptr{ptr}
{
this-> _ref_count = 1;
};
template <typename U, typename Deleter>
control_block(U* ptr, const Deleter& d) : control_block_base(ptr, std::move(d)) ,
_ptr{ptr}
{
this-> _ref_count = 1;
};
~control_block()
{
}
T* get()
{
return _ptr;
}
T* _ptr;
};
template <typename T>
class shared_ptr
{
using element_type = T;
template <typename>
friend class shared_ptr;
public:
~shared_ptr()
{
if (_cb)
{
_cb->dec();
}
}
constexpr shared_ptr() noexcept : _ptr(nullptr), _cb(nullptr)
{
}
constexpr shared_ptr(std::nullptr_t ptr) noexcept : _cb(nullptr), _ptr(nullptr)
{
}
explicit shared_ptr(T* ptr) : _ptr(ptr), _cb (new control_block<T>(ptr))
{
}
shared_ptr(const shared_ptr<T>& other)
{
if (( _cb = other._cb))
{
++_cb->_ref_count;
}
}
template <typename Deleter>
shared_ptr(T* ptr, Deleter d) : _ptr(ptr), _cb(new control_block<T>(ptr, d))
{
}
template <typename U>
explicit shared_ptr(U* ptr) : _ptr(ptr),
_cb (new control_block<U>(ptr, std::default_delete<U>()))
{
}
template <typename U, typename Deleter>
shared_ptr(U* ptr, Deleter d) : _ptr(ptr),
_cb (new control_block<U>(ptr, d))
{
}
template <typename U>
shared_ptr(shared_ptr<U>& u, element_type* e) : _ptr(e), _cb(u._cb)
{
if (_cb)
{
++_cb->_ref_count;
}
};
template <typename U>
shared_ptr(const shared_ptr<U>& u) : _ptr(u._ptr), _cb(u._cb)
{
if (_cb)
{
++_cb->_ref_count;
}
};
template <typename U, typename D>
shared_ptr(std::unique_ptr<U,D>&& up) : _cb(new control_block<U>(up.get(), up.get_deleter()))
{
}
template <typename U>
bool operator==(const shared_ptr<U>& other) const
{
return _ptr == other._ptr;
}
template <typename U>
bool operator!=(const shared_ptr<U>& other) const
{
return !(*this == other);
}
explicit operator bool() const noexcept
{
return _ptr != nullptr;
}
T* operator->() const noexcept
{
return _ptr;
}
T& operator*() const noexcept
{
return *_ptr;
}
void swap(shared_ptr& other)
{
std::swap(_cb, other._cb);
std::swap(_ptr, other._ptr);
}
shared_ptr& operator=(const shared_ptr& other)
{
shared_ptr tmp(other);
swap(tmp);
return *this;
}
// Assignment, copy swap idiom
template <typename U>
shared_ptr<T>& operator=(const shared_ptr<U>& other)
{
shared_ptr temp(other);
this->swap(temp);
return *this;
}
template <typename U>
shared_ptr<T>& operator=(shared_ptr<U>&& other)
{
shared_ptr temp(std::move(other));
this->swap(temp);
return *this;
}
T* get() const noexcept
{
return _ptr;
}
void reset()
{
if (_cb)
{
_cb->dec();
}
}
// why is return value not unsigned?
long use_count() const
{
return (_cb ? _cb->_ref_count.load() : 0);
}
template <typename U>
friend std::ostream& operator<<(std::ostream& os, shared_ptr<U>& sp);
private:
T* _ptr = nullptr;
control_block_base *_cb = nullptr;
};
// This is merely for debugging, useful though.
template <typename U>
std::ostream& operator<<(std::ostream& os, shared_ptr<U>& sp)
{
os << "use_count: ";
if (sp._cb)
{
int status_ignored;
os << sp.use_count()
<< ", shared_ptr<T="
<< abi::__cxa_demangle(typeid(*(static_cast<control_block<U>*>((sp._cb))->_ptr)).name(),NULL,NULL,&status_ignored)
<< ", U="
<< sp._cb->_true_type
<< "> , _ptr="
<< sp._ptr;
}
else
{
os << "nullptr/empty";
}
return os;
};
template <typename Deleter, typename U>
Deleter* get_deleter(const shared_ptr<U>& up)
{
void* deleter = nullptr;
return (Deleter*) deleter;
}
}
And here is a main with some dummy test classes (base and derived). The destructor for base is deliberately non virtual to test the type erasure (deleter in control block).
Feel free to comment, its a first implementation.
#include <iostream>
#include <memory>
#include <ostream>
#include "shared_ptr.h"
#include <cassert>
using namespace std;
int base_counter = 0;
int derived_counter = 0;
struct base
{
// non virtual, deliberately, shared_ptr's deleter will delete in terms of the correct type, T or U
~base()
{
//cout << " base::~base() " << endl;
--base_counter;
}
base()
{
// cout << "base::base() " << endl;
++base_counter;
}
base(int i) : _i(i)
{
++base_counter;
// cout << "base::base() " << endl;
}
void foo()
{
// cout << "base::foo() " << endl;
}
int get()
{
return _i;
}
int _i;
};
struct derived : public base
{
derived()
{
// cout << "derived::derived() " << endl;
++derived_counter;
};
derived(int i) : base(i)
{
++derived_counter;
// cout << "derived::derived() " << endl;
};
// non virtual
~derived()
{
--derived_counter;
// cout << " derived::~derived() " << endl;
};
void foo()
{
// cout << "derived::foo() " << endl;
}
};
void delit(derived* d)
{
//cout << "-------------> delit(derived*) called " << endl;
delete d;
}
void dbase(base* b)
{
//cout << "-------------> delit(base*) called " << endl;
delete b;
}
int main()
{
int i = 0;
assert(derived_counter==0 && base_counter == 0);
cout << endl << endl;
{
cout << "TEST " << i++ << " ";
graham::shared_ptr<base> sp;
cout << "sp: " << sp << endl;
}
assert(derived_counter==0 && base_counter == 0);
{
graham::shared_ptr<base> bp (new base());
bp.reset();
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << " " ;
graham::shared_ptr<base> bp (new base(), &dbase);
using Deleter = decltype(dbase);
Deleter* deleter = graham::get_deleter<Deleter, base>(bp);
int status;
std::string realname = abi::__cxa_demangle(typeid(deleter).name(), NULL, NULL, &status);
cout << "Deleter: " << realname << endl;
bp.reset();
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << " " ;
graham::shared_ptr<base> bp2 (new derived(), delit);
assert(derived_counter==1 && base_counter == 1);
cout << "bp2: " << bp2 << endl;
bp2.reset();
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp2 (new derived(), [] (derived* b){ delete b;});
bp2.reset();
}
assert(derived_counter==0 && base_counter == 0);
{
// ~base() is not virtual, hence derived will not be destructed, deliberate
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp2 (new derived(), [] (base* b){ delete b;});
bp2.reset();
}
assert(derived_counter==1 && base_counter == 0);
--derived_counter;
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp2 (new derived(), [] (derived* d){ delete d;});
bp2.reset();
assert(derived_counter==0 && base_counter == 0);
}
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp2 (new derived());
bp2.reset();
assert(derived_counter==0 && base_counter == 0);
}
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp2 (new derived());
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST (alias ctor) " << i++ << endl;
base* bp2 = new base();
base* bp3 = new base();
{
graham::shared_ptr<base> bsp (bp2 );
graham::shared_ptr<base> bsp2 (bsp,bp3 );
}
assert(derived_counter==0 && base_counter == 1);
delete bp3;
assert(derived_counter==0 && base_counter == 0);
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp;
graham::shared_ptr<base> bp2(bp);
graham::shared_ptr<base> bp3(new base());
bp = bp3;
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
shared_ptr<base> bp(new derived());
shared_ptr<base> bp2(bp);
assert(bp == bp2);
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
shared_ptr<base> bp(nullptr);
shared_ptr<base> bp2(bp);
assert(bp2.get() == nullptr);
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp;
graham::shared_ptr<base> bp2(bp);
assert(bp == bp2);
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<derived> bp(new derived());
graham::shared_ptr<base> bp2(bp);
assert(bp == bp2);
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<derived> bp(new derived());
graham::shared_ptr<base> bp2;
bp2 = bp;
assert(bp2 == bp);
}
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<derived> bp(new derived(3));
graham::shared_ptr<base> bp2;
bp2 = std::move(bp);
assert(bp2->_i == 3);
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<derived> bp;
assert(bp.get() == nullptr);
graham::shared_ptr<derived> bp2(new derived());
if (!bp2)
{
assert(false);
}
}
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<derived> bp(new derived());
graham::shared_ptr<base> bp2;
bp2 = bp;
graham::shared_ptr<base> bp3(new derived());
graham::shared_ptr<base> bp4(new derived());
graham::shared_ptr<base> bp5(new derived());
graham::shared_ptr<base> bp6(new derived());
graham::shared_ptr<base> bp7(new base());
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<derived> bp (new derived());
graham::shared_ptr<base> bp1;
graham::shared_ptr<base> bp2;
graham::shared_ptr<base> bp3;
graham::shared_ptr<base> bp4;
bp1 = bp;
bp2 = bp1;
bp3 = bp2;
bp4 = bp3;
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<derived> dp (new derived());
graham::shared_ptr<derived> dp2 (new derived());
graham::shared_ptr<base> bp1(new base());
graham::shared_ptr<base> bp2(new base());
graham::shared_ptr<base> bp3(new base());
graham::shared_ptr<base> bp4(new base());
}
assert(derived_counter==0 && base_counter == 0);
{
cout << "TEST " << i++ << endl;
graham::shared_ptr<base> bp(new base(123));
graham::shared_ptr<base> bp2(new base(321));
bp.swap(bp2);
assert(bp->get() == 321);
assert(bp2->get() == 123);
}
assert(derived_counter==0 && base_counter == 0);
return 0;
}
<cxxabi.h>
? That's not a standard C++ header. \$\endgroup\$