A Template for Reference Counting



August 01, 1997
URL:http://www.drdobbs.com/cpp/a-template-for-reference-counting/184403382

Reference counting is an object-oriented concept that improves efficiency by allowing objects that are assigned to each other to share the same memory. For complex objects, significant performance gain can be realized by deferring the expensive copy operation until the values are actually changed, if ever. With this technique, you can eliminate the use of pointers and still be able to copy large objects without incurring any unnecessary performance penalty. To implement a class that supports reference counting, a shared data structure must be declared to store variables that normally would have been created as member objects in the class. These objects cannot be declared as class members because they must be visible and shared among all instances of the class. Access to the shared data is provided by a class function that returns a pointer to the shared structure. A pointer to a reference counter is also needed to keep track of the number of class objects referencing the shared data structure. Although the reference counter itself is not a class member, the pointer to the reference counter is a class member.

When a new reference object (class instance) is constructed, memory is allocated for a reference counter and a shared data structure. The object initializes its pointers with the addresses of the allocated memory. The reference counter is initialized to a count of one. The reference counter increments when another reference object is assigned the current reference object (using an assignment operator or a copy constructor).

If two or more objects reference the same data, and one of the reference objects threatens to change the value stored in a data object, the object that wants to make the change can no longer reference the shared data. It must allocate memory for its own data structure and reference counter, copy the contents of the shared object, and decrement its associated reference counter. Only then can it change the stored value.

The reference counter also decrements when a reference object is destroyed. When the last reference object is destroyed, the memory for the shared data structure, as well as the memory for the reference counter, is deallocated.

The Template Class

To implement reference counting reusably, I employed the C++ template feature to develop a generic base class. C++ templates offer the search-and-replace behavior of C++ macros, with the added benefit of strong type checking. See Listing One for the source code. The template implements all the mechanics necessary to support reference counting. In addition, to enforce data encapsulation, all methods related to reference counting are private and thus hidden from programmers. The function GetData provides the only way to access the shared data structure. GetData is overloaded with both constant and a non-constant versions. The const GetData accesses member objects for read-only purposes. The non-const GetData calls the function CopyOnWrite, which duplicates the member objects whenever the reference count is greater than one. The C++ compiler determines which GetData function to use based on whether or not the const keyword is declared in the calling function. This is good OO design because it encourages programmers to differentiate between data access and data manipulation functions. Failure to declare a read-only function constant will still generate correct code, but it instructs the compiler to use the non-constant version of GetData, which will be less efficient if the data values have to be first duplicated.

Listing One

#ifndef __RefCount_h__
#define __RefCount_h__

template<class DATA>
class CHReferenceCount
{
public:
    CHReferenceCount();
    CHReferenceCount(const CHReferenceCount<DATA>& objRefCount);
    ~CHReferenceCount();
    CHReferenceCount<DATA>&
      operator=(const CHReferenceCount<DATA> &robjRefCount);
    void Reset();

protected:
    inline const DATA* const GetData() const;
    inline DATA* const GetData();

private:
    // Reference counting implementation
    void CopyOnWrite();
    void CopyReference(const CHReferenceCount<DATA>& robjRefCount);
    void NewReference();
    void ReleaseReference();
    
    inline int ReferenceCount();

    DATA*  m_pRefData;  // pointer to shared data structure
    int*  m_pnRefCount; // pointer to reference counter
};

///////////////////////////////////////////////////////////////////
// inline functions

template<class DATA>
inline const DATA* const CHReferenceCount<DATA>::GetData() const
{
    return m_pRefData;
}

template<class DATA>
inline DATA* const CHReferenceCount<DATA>::GetData()
{
    if (ReferenceCount() > 1)
        CopyOnWrite();
    return m_pRefData;
}

template<class DATA>
inline int CHReferenceCount<DATA>::ReferenceCount()
{
    return *m_pnRefCount;
}

///////////////////////////////////////////////////////////////////
// out-of-line functions
template<class DATA>
CHReferenceCount<DATA>::CHReferenceCount()
{
    NewReference();
}

template<class DATA>
CHReferenceCount<DATA>::CHReferenceCount
    (const CHReferenceCount<DATA>& robjRefCount)
{ 
    CopyReference(robjRefCount);
}

template<class DATA>
CHReferenceCount<DATA>::~CHReferenceCount()
{
    ReleaseReference();
}

template<class DATA>
CHReferenceCount<DATA>&

CHReferenceCount<DATA>::operator=
    (const CHReferenceCount<DATA>& robjRefCount)
{
    if (robjRefCount.m_pRefData != m_pRefData)
    {
        ReleaseReference();
        CopyReference(robjRefCount);
    }

    return(*this);
}

template<class DATA>
void CHReferenceCount<DATA>::CopyReference
    (const CHReferenceCount<DATA>& robjRefCount)
{
    ASSERT(robjRefCount.m_pRefData && robjRefCount.m_pnRefCount);
    
    m_pRefData = robjRefCount.m_pRefData;
    m_pnRefCount = robjRefCount.m_pnRefCount;
    ++*m_pnRefCount;
}

template<class DATA>
void CHReferenceCount<DATA>::NewReference()
{
    m_pRefData = new DATA;
    m_pnRefCount = new int(1);
}

template<class DATA>
void CHReferenceCount<DATA>::ReleaseReference()
{
    --*m_pnRefCount;
    
    if (*m_pnRefCount == 0)
    {
        delete m_pnRefCount;
        m_pnRefCount=NULL;

        delete m_pRefData;
        m_pRefData=NULL;
    }
}

template<class DATA>
void CHReferenceCount<DATA>::CopyOnWrite()
{
    DATA* pRefData = new DATA(*m_pRefData);

    ReleaseReference();
    
    m_pRefData = pRefData;
    m_pnRefCount = new int(1);
}

template<class DATA>
void CHReferenceCount<DATA>::Reset()
{
    ReleaseReference();
    m_pRefData =  new DATA;
    m_pnRefCount = new int(1);
}

#endif
//End of File

Listing Two shows an implementation of class Company using the template. Notice that a structure is declared for storing the member objects of Company. The copy constructor in the structure is crucial since it is used by CopyOnWrite to duplicate a copy of the data. (Depending on the compiler, failure to declare a copy structure may cause a compile error.) If the member objects need to be initialized, the default constructor for the class must do so. There are no other restrictions that I can think of for using the template. This class was tested with Microsoft Visual C++ (V4.2 and 5.0). It should work also with any compiler that supports templates. I recommend using reference counting for classes that contain long strings or arrays. For classes with simple data types, you may not see the benefits because of the overhead in implementing the reference counter and the extra level of indirection needed to access data members. o

Listing Two: Implementation of Company using referencecount template

#include <stdio.h>
#include <string.h>
#include <memory.h>
#include "refcount.h"

///////////////////////////////////////////////////////////////////
// Data structure for storing variables for class Company
struct SCompany
{
    SCompany()
    {
        memset(m_szCompanyName, 0, sizeof(m_szCompanyName));
        memset(m_szAddress, 0, sizeof(m_szAddress));
    }

    SCompany(const SCompany& struCompany)
    {
        strcpy(m_szCompanyName, struCompany.m_szCompanyName);
        strcpy(m_szAddress, struCompany.m_szAddress);
    }

    char m_szCompanyName[81];
    char m_szAddress[128];
};

class Company : public CHReferenceCount<SCompany>
{
public:
    // Data accessing functions - using const GetData()
    const char* GetCompanyName() const
        { return GetData()->m_szCompanyName; }
    const char* GetAddress() const
        { return GetData()->m_szAddress; }
    // Data manipulation functions - using non-const GetData()
    void SetCompanyName(const char* szCompanyName)
        { strcpy(GetData()->m_szCompanyName, szCompanyName);}
    void SetAddress(const char* szAddress)
        { strcpy(GetData()->m_szAddress, szAddress);}
};

int main()
{
    Company objCompany;
    objCompany.SetCompanyName("ABC Company");
    objCompany.SetAddress("100 Main Street, Nowhereville USA");
    printf("Company name: %s\n",objCompany.GetCompanyName());
    printf("Address     : %s\n",objCompany.GetAddress());
    return 0;
}
//End of File

Kenneth Ngai is a project leader in the Technology department at Johnson & Higgins, an insurance brokerage firm in New York. His projects include developing MFC extension libraries for client/server database applications. Kenneth Ngai has a BS in Industrial Engineering from Columbia University and can be reached via email at [email protected].

August 1997/A Template for Reference Counting/Listing 2

Listing 2: Implementation of Company using referencecount template

#include <stdio.h>
#include <string.h>
#include <memory.h>
#include "refcount.h"

///////////////////////////////////////////////////////////////////
// Data structure for storing variables for class Company
struct SCompany
{
    SCompany()
    {
        memset(m_szCompanyName, 0, sizeof(m_szCompanyName));
        memset(m_szAddress, 0, sizeof(m_szAddress));
    }

    SCompany(const SCompany& struCompany)
    {
        strcpy(m_szCompanyName, struCompany.m_szCompanyName);
        strcpy(m_szAddress, struCompany.m_szAddress);
    }

    char m_szCompanyName[81];
    char m_szAddress[128];
};

class Company : public CHReferenceCount<SCompany>
{
public:
    // Data accessing functions - using const GetData()
    const char* GetCompanyName() const
        { return GetData()->m_szCompanyName; }
    const char* GetAddress() const
        { return GetData()->m_szAddress; }
    // Data manipulation functions - using non-const GetData()
    void SetCompanyName(const char* szCompanyName)
        { strcpy(GetData()->m_szCompanyName, szCompanyName);}
    void SetAddress(const char* szAddress)
        { strcpy(GetData()->m_szAddress, szAddress);}
};

int main()
{
    Company objCompany;
    objCompany.SetCompanyName("ABC Company");
    objCompany.SetAddress("100 Main Street, Nowhereville USA");
    printf("Company name: %s\n",objCompany.GetCompanyName());
    printf("Address     : %s\n",objCompany.GetAddress());
    return 0;
}
//End of File

August 1997/A Template for Reference Counting

A Template for Reference Counting

Kenneth Ngai

Reference counting is a well known technique for improving performance, particularly when manipulating large or complex objects. Less well known are all the problems that arise in debugging reference-counting code — unless you can get someone else to do it for you.


Reference counting is an object-oriented concept that improves efficiency by allowing objects that are assigned to each other to share the same memory. For complex objects, significant performance gain can be realized by deferring the expensive copy operation until the values are actually changed, if ever. With this technique, you can eliminate the use of pointers and still be able to copy large objects without incurring any unnecessary performance penalty. To implement a class that supports reference counting, a shared data structure must be declared to store variables that normally would have been created as member objects in the class. These objects cannot be declared as class members because they must be visible and shared among all instances of the class. Access to the shared data is provided by a class function that returns a pointer to the shared structure. A pointer to a reference counter is also needed to keep track of the number of class objects referencing the shared data structure. Although the reference counter itself is not a class member, the pointer to the reference counter is a class member.

When a new reference object (class instance) is constructed, memory is allocated for a reference counter and a shared data structure. The object initializes its pointers with the addresses of the allocated memory. The reference counter is initialized to a count of one. The reference counter increments when another reference object is assigned the current reference object (using an assignment operator or a copy constructor).

If two or more objects reference the same data, and one of the reference objects threatens to change the value stored in a data object, the object that wants to make the change can no longer reference the shared data. It must allocate memory for its own data structure and reference counter, copy the contents of the shared object, and decrement its associated reference counter. Only then can it change the stored value.

The reference counter also decrements when a reference object is destroyed. When the last reference object is destroyed, the memory for the shared data structure, as well as the memory for the reference counter, is deallocated.

The Template Class

To implement reference counting reusably, I employed the C++ template feature to develop a generic base class. C++ templates offer the search-and-replace behavior of C++ macros, with the added benefit of strong type checking. See Listing 1 for the source code. The template implements all the mechanics necessary to support reference counting. In addition, to enforce data encapsulation, all methods related to reference counting are private and thus hidden from programmers. The function GetData provides the only way to access the shared data structure. GetData is overloaded with both constant and a non-constant versions. The const GetData accesses member objects for read-only purposes. The non-const GetData calls the function CopyOnWrite, which duplicates the member objects whenever the reference count is greater than one. The C++ compiler determines which GetData function to use based on whether or not the const keyword is declared in the calling function. This is good OO design because it encourages programmers to differentiate between data access and data manipulation functions. Failure to declare a read-only function constant will still generate correct code, but it instructs the compiler to use the non-constant version of GetData, which will be less efficient if the data values have to be first duplicated.

Listing 2 shows an implementation of class Company using the template. Notice that a structure is declared for storing the member objects of Company. The copy constructor in the structure is crucial since it is used by CopyOnWrite to duplicate a copy of the data. (Depending on the compiler, failure to declare a copy structure may cause a compile error.) If the member objects need to be initialized, the default constructor for the class must do so. There are no other restrictions that I can think of for using the template. This class was tested with Microsoft Visual C++ (V4.2 and 5.0). It should work also with any compiler that supports templates. I recommend using reference counting for classes that contain long strings or arrays. For classes with simple data types, you may not see the benefits because of the overhead in implementing the reference counter and the extra level of indirection needed to access data members. o

Kenneth Ngai is a project leader in the Technology department at Johnson & Higgins, an insurance brokerage firm in New York. His projects include developing MFC extension libraries for client/server database applications. Kenneth Ngai has a BS in Industrial Engineering from Columbia University and can be reached via email at [email protected].

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.