check out my smart pointer class

Started by
12 comments, last by Zahlman 15 years, 4 months ago
I wrote a reference-counted smart pointer. Please have a look and let me know if you see any problems. The template specialization is to force only pointer types to be allowed. It could use an operator* or GetPtr function to get the raw pointer but I am not sure I'll need it. It could also use a way to set a pointer to null, possibly operator=(int anIntEqualToNull). I would like to hide the InstanceCounted class from the outside world but the only way I can think of is to make all of its methods private and make SmartPtr a friend but then if I write another SmartPtr class the new one will have to be added also. Any advice is welcome. Thanks

#pragma once
#ifndef SMARTPOINTERS_H
#define SMARTPOINTERS_H

#include <cassert>

template<typename T>
class InstanceCounted
{
	InstanceCounted();
	InstanceCounted(const InstanceCounted&);
};

template<typename T>
class InstanceCounted<T*>
{
public:
	explicit InstanceCounted(T* pT) : m_pT(pT)
	{
		assert(m_pT != null);
		m_instCount = 1;
	}

	~InstanceCounted()
	{ delete m_pT; }

	void Increment()
	{ ++m_instCount; }

	void Decrement()
	{ --m_instCount; }

	unsigned GetCount()
	{ return m_instCount; }

	T* const GetPtr()
	{ return m_pT; }
	
private:
	InstanceCounted(const InstanceCounted<T*>&);
	InstanceCounted& operator=(const InstanceCounted&);

	T* const m_pT;
	unsigned m_instCount;
};

template<typename T>
class SmartPtr
{
	SmartPtr();
	SmartPtr(const SmartPtr<T>&);
};

template<typename T>
class SmartPtr<T*>
{
public:
	explicit SmartPtr(T* pT)
	{
		m_pInstCounted = new InstanceCounted<T*>(pT);
	}

	explicit SmartPtr(const SmartPtr<T*>& rhs)
	{
		m_pInstCounted = rhs.m_pInstCounted;
		m_pInstCounted->Increment();
	}

	~SmartPtr()
	{
		m_pInstCounted->Decrement();
		if(m_pInstCounted->GetCount() == 0)
		{
			delete m_pInstCounted;
		}
	}

	SmartPtr<T*>& operator=(const SmartPtr<T*>& rhs)
	{
		if(&rhs != this)
		{
			m_pInstCounted->Decrement();
			if(m_pInstCounted->GetCount() == 0)
			{
				delete m_pInstCounted;
			}

			m_pInstCounted = rhs.m_pInstCounted;
			m_pInstCounted->Increment();
		}

		return *this;
	}

	bool operator==(const SmartPtr<T*>& rhs)
	{
		return m_pInstCounted->GetPtr() == rhs.m_pInstCounted->GetPtr();
	}

	bool operator!=(const SmartPtr<T*>& rhs)
	{
		return m_pInstCounted->GetPtr() != rhs.m_pInstCounted->GetPtr();
	}

	T* operator->()
	{
		assert(m_pInstCounted != null);
		return m_pInstCounted->GetPtr();
	}

	const T* operator->() const
	{
		assert(m_pInstCounted != null);
		return m_pInstCounted->GetPtr();
	}

	unsigned GetCount()
	{ return m_pInstCounted->GetCount(); }

private:
	InstanceCounted<T*>* m_pInstCounted;
};

#endif //SMARTPOINTERS_H
Advertisement
Sorry for stating the obvious, but why not use boost::shared_ptr<>?
Quote:Original post by Rydinare
Sorry for stating the obvious, but why not use boost::shared_ptr<>?


Practice, mostly. I figured it would be a relatively small amount of code that could be written relatively quickly. boost::shared_ptr is probably better, but then I don't get the practice.
First bug I saw: You leak memory if the allocation of the count fails.

Moral of the story: Smart pointers are hard to write. Use somebody else's -- especially now that GCC and MSVS both provide std::tr1::shared_ptr.
Quote:Original post by impulsionaudio
Quote:Original post by Rydinare
Sorry for stating the obvious, but why not use boost::shared_ptr<>?


Practice, mostly. I figured it would be a relatively small amount of code that could be written relatively quickly. boost::shared_ptr is probably better, but then I don't get the practice.


Good. That's the answer I was hoping for. Usually I get silly answers like "Boost is too complicated"

Anyway, looking it over, I think the template specialization is unnecessary. I'm assuming you're anticipating someone using it like this:

SmartPtr<int*>

?

Most smart pointer classes do it like this: SmartPtr<int> and the * is implicit.

Other than that and the potential memory leak mentioned above, I think your implementation is reasonable and I don't see any obvious flaws, but I can't say for sure without giving it a run. I think the main thing would be to try some test cases and see if it works.

To be fair, also, I actually wrote my own smart pointer template class for one of my classes, because we weren't allowed to force the teacher to have to download any extra libraries (boost). I created something similar to this.
Quote:Original post by impulsionaudio

I would like to hide the InstanceCounted class from the outside world but the only way I can think of is to make all of its methods private and make SmartPtr a friend but then if I write another SmartPtr class the new one will have to be added also.


class SmartPtr {  struct InstanceCounted {};};
Additionally...

# There is no mechanism to swap pointers.
# You cannot use a custom allocator or deallocator. This is a big thing, since many games use them.
# It looks like it could have problems for SmartPtr<foo> converting to SmartPtr<const foo>

After a bit more thought...

Consider adding operator bool()
Consider adding operator != for T*
Consider adding operator < for sorting purposes
Consider adding operator * to return a reference the object

Consider const correctness generally. Only your operator -> mostly works on a constant object, except it forces the return type to be constant. This may not be what you intended. Just because the smart pointer is constant doesn't mean the thing being pointed to should also be constant. The comparison operations (==, !=, and <) and the informational function get_count should be const since they are perfectly acceptable operations for constant pointers.
One thing that your pointer lacks is support for natural pointer conversions e.g.

SmartPtr<Derived> d = ...;SmartPtr<Base> b = d;// or even:SmartPtr<anything> a = ...SmartPtr<const void> v = a;// (though it's debatable as to whether you'd really want this one)


There are two ways to achieve this. One is to rearrange SmartPtr such that it's private members are:

class SmartPtr{   std::size_t *m_instCount;   T *m_pT;};


This means you would also have to dynamically allocate the reference count. This may or may not be a big deal for you, depending on your usage patterns.

The second way is to remove the template parameter from InstanceCounted and have it hold the count and a pointer-to-void. Then in the SmartPtr member functions you can cast to-and-fro. It feels ickier, but it is still type-safe as far as clients are concerned. You'd have to add some compile-time checks for pointer-to-derived/pointer-to-base conversions, but it's do-able.

You should also only have a single operator->, which is a const member function returning a pointer to T (non-const T). Having the const version is pointless because anyone can do this:

const SmartPtr<T> p = ...;SmartPtr<T> p2(p);


I now have a pointer (p2) that refers to the same thing as the const SmartPtr, so I'm free to call operator-> which acts on the same underlying pointee.

Something else you could try is to add support for a custom "deleter". This actually comes in handy for passing smart-pointers across DLL boundaries, even if the DLL has been compiled with a different C runtime. I'll leave you to figure that one out, though. It's a very-nice-to-have, IMHO.
Thanks for the comments. All very good.

This topic is closed to new replies.

Advertisement