Archive for June, 2015


Perspective

The intended audience are C++ developers and architects, It is assumed that the reader of this article is familiar with object oriented programming and design.

For the purpose of brevity and clarity, thread synchronization aspect is omitted and not discussed in details in this the article.

Introduction

Comparing to high level languages such as C# & Java, C++ has a substantial disadvantage with memory management, C# & Java automate memory management using garbage collectors where with C++ the developer is responsible for allocating and freeing memory, and this, increase code complexity and the total development and debugging time.

This article describe an approach for achieving pseudo garbage collection using C++, this, reduces development and mainly debugging time while keepingthe fine memory control supported by C++.

Fundamentals

To be able to automate memory allocation we have to keep track of the users of a given memory block/object, once all users are done consuming the memory block/object it can be automatically freed.

Keeping tack of the memory/object references is done using a reference counter, the counter is increased each time a new user is consuming the memory and is decreased when consumption is done, when the counter hits zero the memory block/object is automatically freed.

Implementation guidelines

In-order to achieve automated memory management, each object must implement reference counting and enable it’s users/consumers to control it, this is done by implementing the simple IRefCount interface described at ‘Code Snap 1’ bellow, The ‘AddRef()’ method increase the internal reference count of the object and returns the result while ‘Release’ decrease the reference count, on ‘Release’, when the reference count hits zero the object delete it-self from memory, the following simple code snap illustrate this concept:

01 interface IRefCount {
02     virtual unsigned int AddRef(void) = 0;
03     virtual unsigned int Release(void) = 0;
04 };

Code Snap 1

To guarantee the object life-time will be controlled only by it’s reference count, any other type of instantiation should be prevented, and thus, all IRefCount objects must have their constructors and destructor decelerations defined as non public, instead a special method used for instantiation is to be implemented, in ‘Code Snap 2’ bellow the ‘CreateInstance’ method on line #20 is used exactly for that.

The ‘CreateInstance’ method is responsible for allocating the memory required for the object, adding the first reference by calling ‘AddRef()’ and returning the instantiated object to the caller, similarly, the ‘Release’ method on line #13, is responsible for removing the object from memory when the reference count hits zero, the reference count is maintained by the ‘m_uiRefCount’ variable on line #4.

01 class TestObj : public IRefCount
02 {
03 protected:
04     unsigned int m_uiRefCount;
05         
06     TestObj() : m_uiRefCount(0) {}
07     ~TestObj() {}    
08     
09 public:
10     virtual unsigned int AddRef(void) {
11         return ++m_uiRefCount;
12     }
13     virtual unsigned int Release(void) {
14         unsigned int uiRef = –m_uiRefCount;
15         if(0 == uiRef)
16             delete this;
17         return uiRef;
18     }
19     
20     static bool CreateInstance(OUT TestObj** ppObj) {
21         if(0 == (*ppObj = new TestObj()))
22             return false;
23         (*ppObj)->AddRef();
24         return true;
25     }
26 };

Code Snap 2

There are two main pit-falls with reference counting, the first is a reference that is added and never released: a reference leak, this leads to dangling objects in memory ( a memory leak ), the second is an extra call to ‘Release()’ causing a premature disposal of the object which may lead to future access of an already deleted memory block, these problems can easily be avoided by following few simple rules:

  • Add a reference during assignment.
  • Add a reference for objects returned as output parameters.
  • Make sure always to release the reference when stopped using the object.

The following example illustrate implementation of these simple rules:

01 class SomeClass {
02 protected:
03     IRefCount* m_pObj;
04 public:
05     SomeClass() : m_pObj(0) {}
06    ~SomeClass() {
07        if(0 == m_pObj)
08            return;
08        // No need to hold a reference to the object any-more, release it
08        m_pObj->Release();
09    }
10     void set_Object(IRefCount* pObj) {
11         if(0 != m_pObj) {
12            // Release the reference to the existing object before assigning a new value
13             m_pObj->Release();
14        }
15        if(0 != pObj) {
16             m_pObj = pObj;
17            // Add a reference to account for the assignment
18             m_pObj->AddRef();
19        }
20     }
21     bool get_Object(IRefCount** ppObj) {
22         if(0 == m_pObj)
23             return false;
24         *ppObj = m_pObj;
25        // Add a reference to account for the output variable assignment
26         (*ppObj)->AddRef();
27         return true;
28     }
29 };

Code Snap 3

The SmartPtr class

Making sure object references are added and released as needed is tedious and error prone, it is quite easy to forget to release or add a reference, To avoid that, we will use the SmartPtr class, that encapsulate the reference counting logic, this class is described at Code Snap 4 bellow.

00 template<class T_Interface >
01 class SmartPtr
02 {
03 public:
04     SmartPtr() : m_p(0) {
05     }
06
07     SmartPtr(T_Interface* lPtr) : m_p(0) {
08         if (lPtr != 0) {
09             m_p = lPtr;
10             m_p->AddRef();
11         }
12     }
13
14     SmartPtr(const SmartPtr& sp) : m_p((T_Interface*)sp) {
15         if (m_p)
16             m_p->AddRef();
17     }
18
19     ~SmartPtr() {
20         if (m_p) {
21             m_p->Release();
22             m_p = 0;
23         }
24     }
25
26     operator T_Interface*() const {
27         return m_p;
28     }
29
30     T_Interface& operator*() const {
31         _ASSERT(m_p != 0);
32         return *m_p;
33     }
34
35     T_Interface** operator&() {
36         return &m_p;
37     }
38
39     T_Interface* operator->() const {
40         _ASSERT(m_p != 0);
41         return m_p;
42     }
43
44     T_Interface* operator=(T_Interface* lPtr) {
45         if (lPtr == m_p)
46             return m_p;
47         if (0 != m_p)
48             m_p->Release();
49        if(0 != lPtr)
50             lPtr->AddRef();
51         m_p = lPtr;
52         return m_p;
53     }
54
55     T_Interface* operator=(const SmartPtr& sp) {
56         _ASSERT(&sp != 0);
57         if (0 != m_p)
58             m_p->Release();
59         m_p = (T_Interface*)sp;
60         if (m_p)
61             m_p->AddRef();
62         return m_p;
63     }
64
65     void Attach(T_Interface* lPtr) {
66        if (0 == lPtr)
67            return;
68        if (0 != m_p)
69            m_p->Release();
70        m_p = lPtr;
71     }
72
73     T_Interface* Detach() {
74         T_Interface* lPtr = m_p;
75         m_p = 0;
76         return lPtr;
77     }
78
79     void Release() {
80         if (m_p) {
81             m_p->Release();
82             m_p = 0;
83         }
84     }
85
86     T_Interface* m_p;
87 };

Code Snap 4

The following compares a modified version of Code Snap 3 that use the SmartPtr with the original version of the code that does not use the SmartPtr class, as can be seen, usage of the SmartPtr class considerably reduces the lines of code needed by approximately half, no specialized constructor and destructor are needed and the ‘set_Object’ is reduced to a simple assignment operation.

There is one case however, where the SmartPtr class doesn’t automate reference counting, that is, when a reference to the object is passed an an output variable, this is demonstrated at the ‘get_Object’ method on line #8 at the left pane, in this case, after assigning the value to the output variable the reference count must get manually increased.

Using SmartPtr Not using SmartPtr
01 class SomeClass {
02 protected:
03    SmartPtr m_spObj;
04 public:
05     void set_Object(IRefCount* pObj) {
06        m_spObj = pObj;
07     }
08     bool get_Object(IRefCount** ppObj) {
09         if(0 == m_spObj)
10             return false;
11         *ppObj = m_spObj;
12         (*ppObj)->AddRef();
13         return true;
14     }
15 };
16
17
18
19
20
21
22
23
24
25
26
27
01 class SomeClass {
02 protected:
03     IRefCount* m_pObj;
04 public:
05     SomeClass() : m_pObj(0) {}
06    ~SomeClass() {
07        if(0 == m_pObj)
08            return;
09        m_pObj->Release();
10    }
11     void set_Object(IRefCount* pObj) {
12         if(0 != m_pObj) {
13             m_pObj->Release();
14        }
15        if(0 != pObj) {
16             m_pObj = pObj;
17             m_pObj->AddRef();
18        }
19     }
20     bool get_Object(IRefCount** ppObj) {
21         if(0 == m_pObj)
22             return false;
23         *ppObj = m_pObj;
24         (*ppObj)->AddRef();
25         return true;
26     }
27 };

Code Snap 5

There is however one more limitation we need to solve: In order to achieve the automated memory management logic described in this article, as can be seen at Code Snap 2 above, each and every object must implement reference counting, and this, is cumbersome and time consuming, to solve this we will use the RefCountObj class described bellow.

The RefCountObj class

Code Snap 6 bellow present the RefCountObj class which encapsulate the reference counting logic abstracting out all that is needed from the object but the definition of a reference counting interface, in other words, all that is needed from the object is to have two virtual methods to maintain reference counting, namely ‘AddRef’ and ‘Release’ as described by the ‘IRefCount’ interface for Code Snap 1.

01 template< typename T >
02 class RefCountObj : public T
03 {
04 public:
05     static bool CreateInstance(OUT RefCountObj*& pObj) {
06         pObj = new RefCountObj();
07         return (0 != pObj);
08     }
09
10     static bool CreateInstance(OUT SmartPtr& spObj) {
11         RefCountObj* pObj = 0;
12         bool bRet = RefCountObj::CreateInstance(pObj);
13         if (true == bRet)
14             spObj = pObj;// Adds a reference
15         return bRet;
16     }
17
18     unsigned int AddRef(void) {
19         return ++m_uiRef;
20     }
21
22     unsigned int Release(void) {
23         unsigned int uiRef = –m_uiRef;
24         if (0 == uiRef)
25             delete this;
26         return uiRef;
27     }
28
29     protected:
30         std::atomic_uint m_uiRef;
31
32         RefCountObj() : m_uiRef(0) {}
33         virtual ~RefCountObj() {}
34 };

Code Snap 6

The following demonstrate ‘RefCountObj’ usage, on the right pane is Code Snap 2 added code for instantiation on line #30 of the main function, on the left pane is the same code having RefCountObj used to encapsulate the reference counting logic.
As can be seen ‘RefCountObj’ usage reduced the code to the very basic class definition by abstracting out all reference counting logic, this way, the implemented class encapsulate specific use-cases with no need of dealing with reference counting.

There is one exception to the above mentioned, all objects to be used with automated memory management must have pure virtual ‘AddRef’ and ‘Release’ methods, or, directly inherit from the IRefCount interface defined at Code Snap 1 above, the methods signature must correspond with those defined at ‘RefCountObj’.

Using RefCountObj Not using RefCountObj
class TestObj : public IRefCount
{
protected:
public:
};

void main(void) {
    SmartPtr spObj;
    if(!RefCountObj::CreateInstance(spObj))
        return -1;
    return 0;
}

 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 

 
 
 
 
 
 

class TestObj : public IRefCount
{
protected:
    unsigned int m_uiRefCount;
        
    TestObj() : m_uiRefCount(0) {}
    ~TestObj() {}    
    
public:
    virtual unsigned int AddRef(void) {
        return ++m_uiRefCount;
    }
    virtual unsigned int Release(void) {
        unsigned int uiRef;
        uiRef = –m_uiRefCount;
        if(0 == uiRef)
            delete this;
        return uiRef;
    }
    
    static bool CreateInstance(TestObj** ppObj)
    {
        *ppObj = new TestObj();
        if(0 == *ppObj)
            return false;
        (*ppObj)->AddRef();
        return true;
    }
};

void main(void) {
    SmartPtr spObj;
    if(!TestObj::CreateInstance(&spObj))
        return -1;
    return 0;
}

Code Snap 7

Main Advantages over std::shared_ptr

  • Higher efficiency: Reference count is implemented by each object and not allocated by the smart pointer as with std::shared_ptr reducing memory fragmentation and emitted assembly code execution time
  • DLL Concurrency: The object must implement allocation and de-allocation of it’s memory, this, guarantee cross DLL safety, hence, an object allocated on the heap of one DLL is guaranteed to be deleted from the same heap where with std::shared_ptr one can set a pointer allocated on one DLL to a std::shared_ptr maintained by another, upon std::shared_ptr deletion of the referred object an invalid heap will be used for deletion, causing a memory leak at best, and a crash on worst