FBB::RefCount(3) Base class implementing reference counting.

SYNOPSIS

#include <bobcat/refcount>
Linking option: -lbobcat

DESCRIPTION

RefCount implements a virtual base class implementing reference counting. When reference counting is used, objects share the memory of a (usually big or complex) data structure, until objects need to modify their data, in which case they obtain a copy of the data of their own. This approach is most useful with classes that seldomly alter their data, but consult their data most of the time.

As an example, consider hidden structures as found in the regcomp(3) function: there is no documented way to copy an existing compiled regular expression, so if multiple objects must refer to the same regular expression, the expression should be compiled once. Thereafter, a reference count monitors the number of objects using the compiled expression. Only when the last object is destroyed the compiled expression is freed.

In general, objects using reference counting should obtain their own data if they need to alter their data. This situation is also called `copy-on-write'. Copy-on-write is implemented by obtaining a copy of the data before any modification of the data takes place. So, each non-const member function should first copy the data, and should only then modify its own data. Constant members may simply refer to the data, without the need to copy them first.

The class RefCount should be embedded in programs as follows:

  • RefCount defines a virtual base class, defining all members that are required to implement reference counting. The class is a virtual base class since it defines a member virtual RefCount *clone() const = 0, which must return a real copy of the derived class object (cf. Gamma et al.'s (1995) proxy Design Pattern).
  • From RefCount a class (named Data in this man page) is derived defining the functionality that is required to manipulate the data: this class may contain constructors, overloaded operators, accessors, modifiers, etc.
  • A third class (called Client in this man page) defines the actual interface to the program in which reference counting is required. In its purest form, Client only has a Data * data member referring to the (possibly shared) data. The functionality offered to the users of Client objects is normally defined by shadowing the functionality defined by Data, using simple one-line in-line functions.

Except for clone(), there are several issues to bear in mind when using reference counting as defined by RefCount:

  • When Client share data (e.g., when using a copy constructor or an overloaded assignment operator), RefCount::share() should be called rather than operator new. Since at this point the data are shared with other objects, no copy is required, and the use of operator new should be avoided.
  • When Client objects go out of scope, the destructor should call RefCount::release() rather than operator delete to disassociate itself from the object's data. The operator delete should not be called directly, since this might prematurely destroy data shared with other objects.
  • All members modifying data (i.e., all non-const member functions) should call RefCount::modifying() prior to performing the modification. This ensures that the object operates on its own data, rather than modifying shared data. Except for the abovementioned items, all members of Client should be implemented as usual: constructors use new Data(argument list), clone() returns a pointer to a clone of itself, etc.. Refer to the code example for an actual implementation.

NAMESPACE

FBB
All constructors, members, operators and manipulators, mentioned in this man-page, are defined in the namespace FBB.

INHERITS FROM

-

PROTECTED CONSTRUCTORS

  • RefCount():
    This constructor implements the default constructor.
  • RefCount(RefCount const &other):
    This constructor implements the copy constructor, which is a convenience function for Data's copy constructor, but otherwise acting identically as RefCount() itself.

PROTECTED DESTRUCTOR

  • virtual ~RefCount():
    The destructor is an empty virtual member, thus allowing RefCount * variables to destroy any derived class objects they point to.

PUBLIC MEMBER FUNCTIONS

  • size_t refcount() const:
    This member returns the current number of objects sharing the data.
  • void release():
    This member must be called by Client objects that must disassociate themselves from the (possibly shared) data. In practice it is called by the Client's destructor and overloaded assignment operator. It will actually call Data's destructor when the object was the only object still referring to the data.

PUBLIC STATIC MEMBER FUNCTIONS

o
Data *RefCount::share(Data const *ptr):
This member should be called by the constructor of Client objects sharing another client's data. In practice it is called by the client's copy constructor and overloaded assignment operator. It receives the actual pointer to the data as its argument, and returns the new value of the pointer.
Note that Data is not a hard-coded class: the function is implemented as a template member, and so it can be used by every class derived from RefCount.
o
Data &RefCount::modifying(Data **ptr):
This member should be called by Client objects' non-const members, just before modifying data. The function may alter the value of the client's Data * data member. It returns a reference to the data, allowing the client's member function to call the required Data modifier in one single statement, using the member selection operator (dot).
Note that Data is not a hard-coded class: the function is implemented as a template member, and so it can be used by every class derived from RefCount.
This function performs a dynamic_cast, which will always succeed if Data was indeed derived from RefCount. A bad_cast is thrown if the cast fails.

EXAMPLE

The following example illustrates the use the class RefCount. A class Data is derived from RefCount, defining clone(), several standard members (copy constructor, overloaded assignment operator) as private members, and a default constructor, destructor, accessor and modifier member as public members.

The class that is used directly by the program is Client, given next. It defines all standard constructors and members, and it shadows the accessor and modifier members of Data:

Finally, a small program using Client is shown.

/*
                              driver.cc
*/
#include <iostream>
#include <string>
#include <sstream>
#include "../refcount"
using namespace std;
using namespace FBB;
// The class Data uses reference counting. Data are shared until they are
// modified. 
class Data: public RefCount
{
    static size_t s_n;        // count the number of objects in use
    public:
        Data()                  // all other constructors are built like this:
        {                       // using RefCount's default constructor.
            s_n++;
            cout << "Data(), " << s_n << " Data objects created\n";
        }
        virtual ~Data()
        {
            s_n--;
            cout << "~Data(), " << s_n << " Data objects left\n";
        }
        string accessor() const
        {
            ostringstream ostr;
            ostr << "calling Data::accessor(). Data at " << this;
            return ostr.str();
        }
        void modifier()             // a plain modifier
        {
            cout << "calling Data::modifier(). Data at " << this << endl;
        }
                                    // support function for operator>>()
        istream &extract(istream &istr) 
        {
            cout << "extraction from istream. " <<
                                    "Enter a non-empty line of text" << endl;
            string s;
            getline(istr, s);
            cout << "Read: " << s << endl;
            return istr;
        }
                                    // another modifier: operator+=()
        Data &operator+=(Data const &rvalue)
        {
            cout << "calling Data::operator+=(): @`" << 
                    this << "' += @`" << &rvalue << "'\n";
            return *this;
        }
    private:
        Data &operator=(Data const &other);   // NI
        Data(Data const &other) // The copy constructor MUST call RefCount's
        :                       // CC. Data(Data) is a convenience function 
                                // for clone() and should not be available to 
            RefCount(other)     // clients, thus enforcing the use of 
        {                           // clone() / share() / release()
            s_n++;
            cout << "Data(Data const &), " << s_n << " Data objects created\n";
        }
        virtual RefCount *clone() const
        {
            cout << "Data::clone()\n";
            return new Data(*this);
        }
};
// Client: uses a pointer to a Data object. It is implemented in an 
// almost standard way. Deviations are:
//  *. Copy():       should call share() rather than new Data(*d_dataPtr)
//  *. Destroy():    should call release() rather than delete d_dataPtr;
//  *. non-const members modifying d_dataPtr's data"    
//                   should call Data:modifying() first.
class Client 
{
                                                     // modifying friend
                                                     // see below at modifier()
    friend istream &operator>>(istream &istr, Client &c)
    {   
        return Data::modifying(&c.d_dataPtr).extract(istr);
    }
    Data *d_dataPtr;   
    public:
            // Constructors, destructor, overloaded assignment operator: all
            // follow my standard copy() / destroy() approach. 
        Client()                    // new object, creates its own data
        :                           // use new Data(...) to initialize.
            d_dataPtr(new Data())
        {}
        ~Client()
        {
            destroy();
        }
        Client(Client const &other)
        {
            copy(other);
        }
        Client &operator=(Client const &other)
        {
            if (this != &other)
            {
                destroy();
                copy(other);
            }
            return *this;
        }
        string accessor() const         // accessors shadow Data's members
        {                               
            return d_dataPtr->accessor();
        }
                                        // modifiers call modifying first
        void modifier()                 // simple modifier
        {                               
            Data::modifying(&d_dataPtr).modifier();
        }
                                        // arithmetic assignment modifier
        Client &operator+=(Client const &other)
        {                               
            Data::modifying(&d_dataPtr).operator+=(*other.d_dataPtr);
            return *this;
        }
    private:
        void copy(Client const &other)  // copying is sharing: call share()
        {
            d_dataPtr = Data::share(other.d_dataPtr);
        }
        void destroy()                  // destroying is disassociation: call
        {                               // release
            d_dataPtr->release();
        }
};
size_t Data::s_n = 0;
Client const operator+(Client const &lvalue, Client const &rvalue)
{
    return Client(lvalue) += rvalue;
}
int main()
{
    cout << "Construction:\n";
    Client c;
    cout << "Extraction c from cin:\n";
    cin >> c;
    cout << "c's Modifier called:\n";
    c.modifier();
    cout << "operator += :\n";
    c += c;
    cout << "operator + :\n";
    c + c;
    cout << "Copy construction:\n";
    Client c2(c);
    cout << "Assignment:\n";
    c = c2;
    cout << "Accessors:\n";
    cout << "access c:  " << c.accessor() << endl;
    cout << "access c2: " << c2.accessor() << endl;
    cout << "operator += :\n";
    c += c;
    cout << "c's Modifier called:\n";
    c.modifier();
    cout << "Accessors:\n";
    cout << "access c:  " << c.accessor() << endl;
    cout << "access c2: " << c2.accessor() << endl;
    cout << "c2's Modifier called:\n";
    c2.modifier();
    cout << "resetting refcount to 2:\n";
    c = c2;
    
    cout << "Extraction c from cin:\n";
    cin >> c;
    cout << "End of program:\n";
}

FILES

bobcat/refcount - defines the class interface

BUGS

None Reported.

DISTRIBUTION FILES

  • bobcat_2.08.01-x.dsc: detached signature;
  • bobcat_2.08.01-x.tar.gz: source archive;
  • bobcat_2.08.01-x_i386.changes: change log;
  • libbobcat1_2.08.01-x_*.deb: debian package holding the libraries;
  • libbobcat1-dev_2.08.01-x_*.deb: debian package holding the libraries, headers and manual pages;
  • http://sourceforge.net/projects/bobcat: public archive location;

BOBCAT

Bobcat is an acronym of `Brokken's Own Base Classes And Templates'.

COPYRIGHT

This is free software, distributed under the terms of the GNU General Public License (GPL).

AUTHOR

Frank B. Brokken ([email protected]).