- Lack of tools - just try to write a C# application using notepad and the command line compiler and you will see how much tools make a managed developer life easy. There is no comparable level of tool support for native code developers, and this may be linked to the next point.
- Lack of comprehensive and cohesive libraries and frameworks - the Win32 API is way too low-level for most uses. It's very nice to be able to use it directly without paying the P/Invoke price, but this comes at a cost of having to write dozens of lines of code to achieve a very simple outcome. The OLE DB and MAPI COM interfaces are a perfect example of this - way too complex to use as they are offered. Just compare the "stock" OLE DB samples with the OLE DB Client library I published here...
- Memory management - this is a minor issue for small applications but when the scale starts to grow it becomes a headache. When are you supposed to delete that object? Is it safe to do it now? Are there other objects referring to it?
There are two basic approaches to automatic memory management that have been used for quite a long time:
- Reference counting - The iPhone uses a reference counting mechanism to determine when a given object can be removed from memory. The price you pay for this type of memory management is that you have to remember to increment and decrement the memory reference counts. Failing to do this will result in memory leaks. The iPhone uses an optional "autorelease pool" object that kind of protects you from this.
- Garbage collection - The Java and .NET platforms both use garbage collection to reclaim the memory of objects that are no longer active. The price you pay for this is that the GC has a mind of its own and stops your code in order to clean up the mess. On mobile devices, if you don't manage your memory by reusing objects and by avoiding excessive allocations, the GC will kill your application performance.
If we couple this with a simple reference counting mechanism, we can actually get all the benefits of a reference-counted memory management scheme without the pain that is imposed on us by the iPhone: we don't have to remember when to increment and decrement the references. How? The dead-old solution is to use reference counted smart pointers to manage our objects' life cycles.
After performing a search for reference-counted smart pointers, and realizing that my needs were quite simple, I decided to roll my own. The first thing we need is a place where to store the reference counts. Some implementations use a proxy object to do this but, for simplicity sake, I decided to put the reference count right on the object through inheritance. This has the disadvantage of having to make all the reference-counted objects inherit from one class, but it's a simple class that will only add four bytes to the object size:
class CRefCount
{
private:
LONG m_nRefCount;
public:
CRefCount(void) : m_nRefCount(0) { };
void RefIncrement()
{
InterlockedIncrement(&m_nRefCount);
}
bool RefDecrement()
{
return InterlockedDecrement(&m_nRefCount) <= 0; } };
Note that I'm using the InterlockedIncrement and InterlockedDecrement to manage the reference count because these methods can be used from different threads. Also, note that the reference count starts at zero (and not at one like on the iPhone) because I want to count how many smart pointers are referring to this object. Let's see how the smart pointer class is defined:
As you can see each smart pointer takes up the same amount of memory as a regular pointer, so you can safely pass and return it by value from your own methods and functions. This is a very bare-bones implementation and may lack some more sophisticated features, but so far has worked quite well for me. You use it like a CComPtr (I confess that I did look it up when writing this code).
template <class T>
class CRefPtr
{
private:
T *p;
public:
CRefPtr() : p(NULL) { }
CRefPtr(T *pT) : p(pT)
{
if(p)
p->RefIncrement();
}
CRefPtr(const CRefPtr& ptr) : p(ptr.p)
{
if(p)
p->RefIncrement();
}
~CRefPtr() throw()
{
Release();
}
void Release()
{
T* pTemp = p;
if(pTemp)
{
p = NULL;
if(pTemp->RefDecrement())
delete pTemp;
}
}
void Attach(T* p1)
{
Release();
p = p1;
}
operator T*() const throw()
{
return p;
}
T& operator*() const
{
ATLENSURE(p!=NULL);
return *p;
}
bool operator!() const throw()
{
return (p == NULL);
}
T* operator=(const CRefPtr& rhs)
{
if(this != &rhs)
{
Release();
p = rhs.p;
if(p)
p->RefIncrement();
}
return *this;
}
T* operator=(T* pT)
{
if(p != pT)
{
Release();
p = pT;
if(p)
p->RefIncrement();
}
return *this;
}
T* operator->() const
{
ATLASSERT(p != NULL);
return p;
}
};