Introduction
With technology like WinRT, Microsoft is putting C++ back on the scene. Every one knows that C++ as a native language produces a fairly
optimized code that will give your applications a good performance if properly used. However one of the drawbacks of C++ is that you have to
manage memory and resources of the system yourself.
For a generation of developers who were breastfed with manage languages like Java, C#, VB.NET, or Python, managing memory by themselves may appear tricky or even has-been!
As WinRT is based on good old COM and that one of the principles of COM is reference counting, I have tried to bring the safety of
reference counting of COM smart pointers to normal C++ class instances. This short article describes a reference counted smart
pointer that uses some of the principles of a COM component. It doesn't pretend to be a perfect solution but it brings some instance management
to the world of native programming and illustrates some of the principles of C++ development and memory management.
Remark: The given solution is for Visual Studio 2012 Premium. However the code for the smart
pointer can be used with any other Visual Studio version and with Windows XP to Windows 8. Visual Studio 2012 has a new unit test
framework for C++ code, which is why this solution needs this version.
Background
It is better to have a good understanding of what a pointer and a reference means in C++ as opposed to a reference in a managed language
like C#, however the goal of this article is also to help developers who are not yet familiar with those concepts to understand them properly.
Other notions of C++ like
operator overloading, usage of the
different types of constructors like copy constructor, as well as a simple template class are used in this article. I will shortly explain
their usage and when it might be a bit tricky.
A simple architecture
My original requirement is to
provide an automatic mechanism to
manage the memory of an object referenced by a pointer and also support
a scenario where the pointer or reference is held by an object and the
object is deleted while its pointer is used by this other object. If
you don't support a counting mechanism, your program will crash as it
would try to use a pointer to an object that has been already deleted.
I have already found some
implementations of smart pointers with
reference counting but they impose that you use the smart pointer class
everywhere you would use a pointer, even in parameters. I wanted to use
a solution similar to the smart pointer of COM where you can still use
a normal pointer in the signature of the method when you pass this
pointer, but where everywhere you use a pointer to your object you put
it in a smart pointer class to manage the life of your reference.
I also tried to make it
compatible with using the delete
operator if
you don't manage the pointer with the smart pointer class.
There are two classes to
implement this smart pointer. One is
something similar to the IUnknown
interface of COM but without the
QueryInterface()
method, and the other class is a template class very
similar to the _com_ptr
class of COM which is the smart pointer used to
manage COM references.
To be managed by my smart
pointer, a class must implement the
abstract class IRefCount
that defines the AddRef()
and Release()
methods.
C++ does't support constraints
for templates like C# so your template
definition may compile until you really instantiate with a concrete
class. In the case of _refcnt_ptr
it expects the class used to
implement the IRefCount
interface. In this article I may refer as interface a class that has
only pure virtual methods. Standard C++ doesn't have interface
keywords like Java or C#. The interface
keyword in C++/CX is an
extension of the language specific to Microsoft and the support of
WinRT in the language.
One of the paradigms of C++ is that it lets the developer completely
define a new type with a class. This new type will then behave exactly
like a native type of the compiler as long as every bit of it has been
created properly. This is why in C++ you can overload each and every
operator of a class, including the
new
and delete
operators as well as the cast
operators. Operators have a very precise behavior in C++
depending on if they apply to a pointer, a reference, or a value object.
For example a simple line of code may automatically invoke operators
and constructors without you realizing it.
The copy constructor in C++ is extremely important and most of the time
your classes must implement it properly, as the compiler is going to
invoke it implicitly and if you don't provide it, it will use a default
bit to bit copy which in most cases won't work.
This simple class diagram shows the hierarchy of classes and the
relations between them. The smart pointer class is a template class, I
described the fact that the template type is a IRefCount
by associating
the _refcnt_ptr
with the IRefCount
interface.
Let's dig into the smart pointer class.
The smart pointer class
The implementation I give here can be used as a base to create a more
complete smart pointer or maybe adapted to specific needs.
A smart pointer needs to behave like a regular pointer when you
use it so it needs to overload some useful operators.
I have overloaded the following ones:
->
so you can call the methods of the wrapped pointer as if
you were using it directly *
so you can write *myPtr
=
because the compiler may invoke it where you don't expect... T*
cast operator so you can automatically extract the underlying
pointer.
It is also important to give a proper copy constructor to your smart
pointer as again the compiler is going to invoke it silently:
template<class T> class _refcnt_ptr sealed : public _refcnt
{
private:
IRefCount* m_ptr;
public:
_refcnt_ptr() : m_ptr(nullptr)
{
}
_refcnt_ptr(const _refcnt_ptr<T>& otherPtr) : m_ptr(otherPtr.m_ptr)
{
_AddRef();;
}
_refcnt_ptr(T* ptr) : m_ptr(ptr)
{
_AddRef();
}
~_refcnt_ptr()
{
_Release();
}
T* operator->()
{
return static_cast<T*>(m_ptr);
}
T& operator* ()
{
return *static_cast<T*>(m_ptr);
}
operator T*()
{
return static_cast<T*>(m_ptr);
}
_refcnt_ptr<T>& operator=(const _refcnt_ptr<T>& otherPtr)
{
if (this != &otherPtr)
{
_Release();
m_ptr = otherPtr.m_ptr;
_AddRef();
}
return *this;
}
private:
void _AddRef()
{
if (m_ptr != nullptr)
{
m_ptr->AddRef(true);
}
}
void _Release()
{
if (m_ptr != nullptr)
{
m_ptr->Release(true);
}
}
};
The complete code is given there but like I said it is a starting
point to illustrate the concept and when using it in different real
situations, it might need to be enhanced.
The wrapped pointer is of type IRefCount
, this is not
necessary as
C++ templates are not type safe like in C# but it makes the code more
readable and the compiler will complain if you try to use it with a non
IRefCount
class. You can write the same using T*
in place of
IRefCount*
, the compiler will complain if your class
doesn't have
the
AddRef()
and Release()
methods.
I have sealed the class as it is a stand-alone class that you won't
inherit.
This smart pointer needs a IRefCount
based class to
work with. Let's
look at the code of this class.
class REFCNTDECL CRefCount : public IRefCount
{
private:
int refCount; int refPtrCount;
protected:
CRefCount();
public:
void operator delete(void *ptr);
virtual void AddRef(bool isSmartPtr = false);
virtual void Release(bool isSmartPtr = false);
protected:
virtual bool CanReleaseRef();
};
This class does a little bit more than counting the number of
references of your object. It is also responsible to delete the object
when there is no more reference used. As a class that is managed by the
smart pointer must inherit from this class, when its method Release
is
called and the reference count is 0, the Release
method behaves like a
'suicide' method as it deletes the object itself.
The AddRed()
and Release()
methods have
a parameter with a default value
set to false. This parameter is used to tell the reference counter if
it is used from a smart pointer. This information is used by the delete
operator implementation and the CanReleaseRef
method.
It is useful for the following scenario:
TEST_METHOD(TestPersonAndAddress)
{
_refcnt_ptr<CPerson> person = new CPerson();
person->SetName(T_OLIVIER);
person->SetSurname(T_ROUIT);
_refcnt_ptr<CAddress> address = new CAddress(T_STREET, T_ZIP, T_CITY);
person->SetAddress(address);
Assert::AreEqual(T_OLIVIER, person->GetName().c_str());
Assert::AreEqual(T_ROUIT, person->GetSurname().c_str());
Assert::AreEqual(T_STREET, person->GetAddress().GetStreet().c_str());
Assert::AreEqual(T_ZIP, person->GetAddress().GetZipCode().c_str());
Assert::AreEqual(T_CITY, person->GetAddress().GetCity().c_str());
_refcnt_ptr<CAddress> refAddress = &person->GetAddress();
delete person;
std::string city = refAddress->GetCity();
}
The pointer instance of CPerson
is managed by the
smart
pointer, so when I call delete
on person
, it would delete the
person and the CAddress
object associated with it. However
because in the
CPerson
class the CAddress
instance is
managed with a smart pointer the
reference to CAddress
I got from the CPerson
instance is still valid
and the program won't crash.
Another effect is as I have overloaded the delete operator and used
a counter of smart pointers monitoring this reference, the delete
call
doesn't delete the CPerson
instance, it lets the smart
pointer do it
when it is out of scope.
Previously I said that the CAddress
would be deleted
when calling
delete person
, but because delete
is in fact not really
deleting the
person
instance, the address reference you have is
protected by two mechanisms.
I only tested a few scenarios and I'm pretty sure that I must have missed
some and that in a real application this smart pointer would have to
be... much more smarter!
Using the debugger on the above unit test method you can see how the
overloaded operators and constructors are called by the compiler on
a simple method call. For example put a break point in
person->SetAddress(address)
and put some break points in _refcnt_ptr code
. You will be surprised how many lines of
the code are
invoked just for this simple call and the single line of code of that
method!
Points of Interest
I have been working with COM and C++ for almost 10 years when COM
was a popular technology and Java was just starting to be used. Then .NET
came and COM was kind of put back to sleep. This was the era of managed
languages where the runtime took care of all the memory hassles
for you, well so you think. It's been now almost 10 years that I'm
working mostly with managed languages like C# and they definitely bring
some more safety in the memory management, but it brings out other
problems that are sometimes more difficult to solve because you don't
any more get a fine control on what you are doing.
So if you have to write WinRT components in C++, the C++/CX compiler
is going to take care of some of the plumbing of COM that was
troublesome in pure C++, but when it comes to your own C++ classes,
you'll have to manage the memory and the life of your objects by
yourself.