>>It could be improved without adding much more to the class if the call to new in the constructor is removed (better exception safety) and if m_refs is allocated only when referencing non-null pointers
You were right All it took was replacing the assert's in my code with if(refCount != NULL).
Here's my updated code:
Code:
#include <cassert>
template <class T>
class RCSmartPtr
{
public:
typedef void (*ZeroRefDeleteProc_t) (T* ptr);
static void genericDelete(T* ptr) {delete ptr;}
static void arrayDelete(T* ptr) {delete[] ptr;}
public:
explicit RCSmartPtr(T* ptr, ZeroRefDeleteProc_t delProc = &genericDelete) :
refCount(NULL), ptr(ptr), zrDeleteProc(delProc)
{
if(ptr != NULL)
refCount = new unsigned long(1);
}
RCSmartPtr(const RCSmartPtr& orig) :refCount(orig.refCount), ptr(orig.ptr), zrDeleteProc(orig.zrDeleteProc)
{
if(refCount != NULL)
++(*refCount);
}
~RCSmartPtr()
{ closeRef(); }
RCSmartPtr& operator=(const RCSmartPtr& orig)
{
if(orig.refCount == refCount) //If assigning to itself, quit
return *this;
closeRef();
ptr = orig.ptr;
zrDeleteProc = orig.zrDeleteProc;
refCount = orig.refCount;
if(refCount != NULL)
++(*refCount);
return *this;
}
T* operator->() const {assert(ptr != NULL); return ptr;}
T& operator*() const {assert(ptr != NULL); return *ptr;}
T& operator[](unsigned long index) const {assert(ptr != NULL); return ptr[index];}
T* get() const {return ptr;}
operator bool() const {return (refCount != NULL);}
protected:
void closeRef()
{
if(refCount != NULL)
{
if(--(*refCount) == 0)
{
delete refCount;
zrDeleteProc(ptr);
ptr = NULL;
}
refCount = NULL;
}
}
ZeroRefDeleteProc_t zrDeleteProc;
unsigned long* refCount;
T* ptr;
};
And a random, garbled mess of assignment/construction/destruction to go with it:
Code:
#include <windows.h>
struct X
{
~X() {MessageBox(NULL, "FJDSLKFJ", "", MB_OK);}
int x, y;
};
int main()
{
MessageBox(NULL, "Create", "",MB_OK);
RCSmartPtr<X> pX(new X[5], &RCSmartPtr<X>::arrayDelete);
pX->x = 5;
pX->y = 10;
const RCSmartPtr<X> x(pX);
const RCSmartPtr<X> y = x;
RCSmartPtr<X> z(NULL);
const RCSmartPtr<X> n = z;
y.~y();
n.~n();
n.~n();
RCSmartPtr<X> m = n;
pX[2].x = 1;
pX[2].y = 3;
std::cout << pX[2].x << pX[2].y << std::endl;
std::cout << pX->x << " " << pX->y << std::endl;
std::vector< RCSmartPtr<X> > vect;
vect.push_back(pX);
vect.push_back(pX);
vect.push_back(pX);
vect.erase(vect.begin() + 2);
vect.push_back(pX);
vect.push_back(pX);
pX = pX;
vect.push_back(pX);
MessageBox(NULL, "Create", "",MB_OK);
RCSmartPtr<X> pY(new X());
pY = pX;
pX.~pX();
pX.~pX();
pX = RCSmartPtr<X>(NULL);
x.~x();
pY.~pY();
MessageBox(NULL, "Begin clear", "", MB_OK);
vect.clear();
MessageBox(NULL, "k", "k", MB_OK);
RCSmartPtr<X> k(new X());
k = RCSmartPtr<X>(new X());
k = RCSmartPtr<X>(NULL);
return 0;
}
Messageboxes should appear in this order:
-Create
-Create
-DSFLKSDJF (destroy)
-Begin Clear
-DFKLSJDLSKF (x5)
-k
-DFDSKLJFSD (x2)
This one allows for NULL pointers without reference counting, and protects against the invalid pointer dereferencing caused by explicit destructor calls (even though you'd really have to be trying if you decided to do that).
>>if the call to new in the constructor is removed (better exception safety)
I have never in my life come across an exception caused by new, so at the moment this is of little concern to me. Anyway, how does it matter where an exception occurs? The object is supposed to get destroyed properly regardless. Unless you're referring to what will happen in the destructor if the members aren't initialized yet?