Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Technique for Active Object Aggregation in C++

0.00/5 (No votes)
3 Feb 2005 1  
Allocation persistance as a supplied behavior of member variables.

Introduction

I'm a big fan of the Active Object pattern. At some level, a good deal of software can be specified in terms of how a set of components respond to events in a system. The description of such components' behaviors can be more easily understood if it is assumed they only occur one at a time. The Active Object simplifies design planning by ensuring that an object's specified behaviors are synchronized. It decouples the moment an event occurs from the moment it is responded to accept events from an asynchronous system.

This article is preliminary to the description of a particular approach to the implementation of Active Objects. I don't ask that you bear with this article unmotivated.

Active Objects often need some control of their own lifetime. One of their most common responsibilities is simply to wait. Components simpler than Active Objects will also make similar commitments. For this first piece, I'm going to present a technique for making classes that can extend their lifetime without becoming coupled with their containing class. While reference counting is often used for classes that must be persisted by patrons of that class, here we will also use reference counting to allow objects to extend their own lifetime or the lifetime of their patrons.

Included source is used in this technique, the source is basically another reference counting implementation. A number of articles on CodeProject already describe reference counting and smart pointers, Peterchen's "Smart Pointers to boost your code" covers some important aspects at a quick pace while introducing a popular programming library's implementation.

Most publicly available reference counting implementations include some smart pointer class. The smart pointer acts like a pointer, but has extra smarts to make sure whatever object is pointed to also has the right lifetime. Smart pointers don't always use reference counting to ensure their allocation is available (nor should they). Reference counting doesn't always imply the use of smart pointers (though I recommend it). The two ideas are symbiotic but can be considered independently. The source I include contains a smart pointer implementation, the reference counting implementations provided can also be used by an included smart pointer class. Boost's instrusive_ptr<> should be useable (note that this interoperability has not been tested).

I must admit, just looking for articles to link here to help readers new to smart pointers, I find material that is new to me. Boost has a good implementation and discussion of smart pointers; if a reader is so interested, they might be better served there. Peterchen has a CodeProject article giving a quick description of Boost smart pointers.

The Loki library described in "Modern C++ Programming" pushes the smart pointer concept a bit further to allow the allocation policies used by smart pointers to be applied to resources besides memory allocations. It is more advanced reading if you are so inclined.

To Intrude? How Rude!

One distinction commonly made in approaches to reference counting is whether or not they are intrusive. Intrusive reference counting schemes require the referenced objects to be built to support a particular reference counting implementation. Non-intrusive reference counting doesn't put such a requirement on referenced classes. Both Boost and Loki support intrusive and non-intrusive reference counting, but focus is weighted towards non-intrusive. Some people consider intrusive counting a bit inelegant, if not downright icky.

Non-intrusive reference counting can be implemented by having a proxy object keep a pointer directed at the target, with all smart pointers tracking that proxy. While the smart pointer tracks the proxy, it behaves like a pointer to the proxy's target. When no one references the proxy anymore, the proxy dereferences the target and itself.

Intrusive implies that the object has a member variable which counts the references for the object. COM components use intrusive reference counting. COM components expose reference operations as virtual functions, and each component implements the reference behavior however it wants. Another approach typical of C++ libraries will have the counted objects inherit from an object which supports the reference operations, with a virtual destructor that is called when the last reference is released. With the latter approach, fewer virtual calls are needed during an object's lifetime. There's also less temptation to couple unexpected behaviors with the reference count operations.

Non-intrusive reference counting is beneficial because classes do not become coupled with the means by which they are reference counted. This is certainly nice, and in cases, the appropriate way to divvy up complexity between a class and its patrons. Intrusive reference counting can almost offer this benefit. Consider an intrusive reference counting scheme which requires objects to inherit from CCountable to be reference counted. Using the reference counting with existing classes is pretty doable:

template <class T>
class ReferenceCounted : public T, private CReferenceCounting
{
    ReferenceCounted()
    {
    }
};

If that works, an instance of ReferenceCounted< CFoo> can be created whenever CFoo is needed, with reference counting behavior. The only limitation I see is the pain in overloading CFoo's constructors. The point being, not having reference countable objects does not prevent someone from using intrusive reference counting.

One advantage of intrusive reference counting is that fewer allocations are required. There's no separate allocation for the proxy. Depending on your performance requirements, this may or may not matter. For optimized asynchronous applications, heap contention is often the last performance pain left when the code is too convoluted to be optimized further. I hate to exploit a programmer's tendency to optimize prematurely in order to sell intrusive reference counting, but heap contention is sometimes an important consideration.

Divided, We Fall

One weakness of most intrusive reference counting schemes is that the objects cannot be aggregated as members of their patron class. A class may inherit from a reference counted object if it is also willing to inherit the reference counting behavior, but it may not contain reference counted objects as member variables. If a member variable reference were to go to zero, it does not know how to delete its container, nor does when.

An easy solution is to always use a reference as a member variable instead of the referenced class directly. Containment through aggregation, however, is semantically different from containment by reference, and that difference can express a very specific property about the relation between two class' lifetime.

The aggregation problem could also be remedied by using non-intrusive reference counting. In this case, support is typically only available to reference the allocated class or some of its ancestors. While the members can be accessed through the container reference, the aggregated class cannot be reference counted without coupling that reference behavior with the patron class. This implies the object cannot reference itself unless it knows about its container, which limits the reuse of that class.

The solution I present here is to have each object's reference behavior redirect reference count changes to their container's reference behavior. The reference count and virtual destructor is replaced by a pointer to the container's reference count and virtual destructor. We can expect reference operations on the object to require looking up the container- but this overhead cost is small compared to the existing overhead of doing a threadsafe reference count modification. The remainder of the object is still referenced directly without a proxy. Supporting reference counting in this manner, objects will be easily allocated on the heap or as member variables of other classes.

Specifying an Indirect Reference Behavior

Typically, a reference counted object gets that behavior by inheriting from a class which supports reference counting. In the next code snippet, CFoo is reference countable because it inherits from ThreadSafeReferenceCountedObject. This works with the included code, though ThreadSafeReferenceCountedObject is really the following typedef:

class CFoo : public ThreadSafeReferenceCountedObject
{
};

typedef ThreadSafeReferenceBehavior< HeapPersistanceBehavior>
    ThreadSafeReferenceCountedObject;

ThreadSafeReferenceCountedObject is a specification of two policies defined in the included code: a reference counting policy (threadsafe counting) and a persistence policy (the alloc is on the heap). We will change these policy choices to make the object aggregateable. There are two ways to aggregate reference counted objects in the included code. They are:

class CFooProxyCount
: public ThreadSafeReferenceBehavior< EmbeddedPeristanceBehavior<
            ThreadSafeReferenceCountedObject > >
{
    CFooProxyCount( ThreadSafeReferenceCountedObject *pAnchor)
    : ThreadSafeReferenceBehavior< 
        EmbeddedPeristanceBehavior< ThreadSafeReferenceCountedObject > >
            (pAnchor)
    {
    }
};

class CFooIndirectCount
: public EmbeddedReferenceBehavior< ThreadSafeReferenceCountedObject>
{
public:
    CFooIndirectCount(ThreadSafeReferenceCountedObject* pAnchor)
    : EmbeddedReferenceBehavior< ThreadSafeReferenceCountedObject>
            (pAnchor)
    {
    }
};

The above two classes can be aggregated with a container whose lifetime is managed by inheriting from ThreadSafeReferenceCountedObject. CFooProxyCount keeps its own reference count, referencing the container once whenever the proxy reference count is nonzero. CFooIndirectCount passes each of its references directly to its container.

This difference is in implementation, not in the provided behavior. EmbeddedReferenceBehavior<> should require fewer interlocked increment/decrements, giving it a slight performance advantage. Using EmbeddedPeristanceBehavior provides more information to debug with, since a clearer picture can be drawn as to what references are holding a container.

The constructors provided are required in order to allow the class to find its container at runtime. By finding the container during construction, the class remains decoupled from the type of its container. All constructor overloads must pass along a pointer to the container.

In some cases, there will be other behaviors of the container that the aggregate could need. The aggregate could use another pointer, set at creation, to access the container's other behavior. An alternative, is to specify a richer class as the embedded reference behavior. In a later implementation of an Active Object, the container type provides callback multiplexing behavior in addition to referencing behavior. That might look like this:

class CActiveObject
: public EmbeddedReferenceBehavior< CMessageQueue>
{
    CActiveObject( CMessageQueue *pAnchor)
    : EmbeddedReferenceBehavior< CMessageQueue>( pAnchor)
    {
    }

    void Start()
    {
        // GetReferenceContext() retrieves the message queue indicated

        // during the constructor.

        GetReferenceContext()->EnqueueMessage();
    }
};

The ThreadSafeReferenceBehavior<> policy indicates that the reference counting is done in a threadsafe manner. ThreadUNSafeReferenceBehavior<> is also an option, though it's been a while since I've found that one useful. It's not threadsafe, but it is faster. This is generally for data processing algorithms that manipulate complex structures within a single thread (like chart parsing). For Active Objects, the reference counting should be threadsafe. CFooProxyCount uses the ThreadSafeReferenceBehavior<> class twice, the second time indirectly through the ThreadSafeReferenceCountedObject typedef. The ThreadSafeReferenceBehavior<> within the ThreadSafeReferenceCountedObject indicates the container has a threadsafe reference count, the first ThreadSafeReferenceBehavior indicates the proxy reference count is also threadsafe.

Some Examples

I have a few more examples to show before I point you at the code. The first shows how our previous aggregateable objects might be included in a reference countable object. The second shows how our previous aggregateable objects can be included in another aggregateable object. Notice how the first constructor passes 'this'. A compiler warning may result. A class described later, CRefCountAnchor, can be used to avoid the isolate and avoid the warning.

class CReferenceCountableContainer
: public ThreadSafeReferenceCountedObject;
{
    CFooProxyCount _foo1;
    CFooIndirectCount _foo2;

public:
    CAggregateableContainer()
    : _foo1( this), _foo2( this)
    {
    }
};


class CAggregateableContainer
: public EmbeddedReferenceBehavior< ThreadSafeReferenceCountedObject>
{
    CFooProxyCount _foo1;
    CFooIndirectCount _foo2;

public:
    CAggregateableContainer( ThreadSafeReferenceCountedObject *pAnchor)
    : _foo1( pAnchor), _foo2( pAnchor),
    EmbeddedReferenceBehavior< ThreadSafeReferenceCountedObject>( pAnchor)
    {
    }
};

Here, CReferenceCountableContainer can be allocated on the stack as is. CAggregateableContainer, CFooProxyCount, CFooIndirectCount and CActiveObject cannot be allocated on the stack. To allocate these on the stack, CRefCountAnchor<> can be used, with the aggregateable type as the template parameter. CRefCountAnchor<> is specific to objects which have an anchor of type ThreadSafeReferenceCountedObject, see its definition to see how to stack allocate objects with a different anchor type.

The code and comments go into more detail about these classes and the requirements for using them. I hope I've given a sense of what is there and when to use them. If you're lost at this point, I'd recommend looking for other articles on smart pointers and reference counting. You may want to read through the code, it has lots of comments. It also has more examples of using reference counted objects.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here