Topic : Smart pointer templates in C++
Author : David Harvey
Page : << Previous 3  Next >>
Go to page :


before decrementing the count for the current (left-hand-side) object. In the case of self-assignment, this will leave the reference count unchanged and consequently prevent deletion of the counted instance.
template<class T>
ObjVar<T>& operator=(const ObjVar<T>& rVar)
{
        if (!rVar.Null())
                rVar.m_pCounted->GetRef();
        UnBind();
        m_pCounted = rVar.m_pCounted;
        return *this;
}

Finally, we declare equality and not-equals operators. Two object variables are equal only if they wrap the same underlying instance. This implements a test for object identity, by comparing the addresses of the objects wrapped inside the counted class. This is exactly what we want for object variables, but might in some cases cause problems in contexts where the equality operator is understood as an equivalence relation (for example, when instantiating STL container classes). In this case, you may prefer to dereference the pointers before testing for equality.
template<class T>
bool operator==(const ObjVar<T>& lhs,
                        const ObjVar<T>& rhs)
{
        return lhs.m_pCounted->my_pT
                        == rhs.m_pCounted->my_pT;
        // or *(lhs.m_pCounted->my_pT) == *(rhs.....)
}

template<class T>
bool operator!=(const ObjVar<T>& lhs,
                        const ObjVar<T>& rhs)
{
        return !(lhs == rhs);
}

Note that the inequality operator does not need to be defined as a friend of the objvar class, as it simply negates the result of the test for equality.
With a little ingenuity, it is also possible to implement object variables without an intermediate reference-counted class. While this saves a level of indirection in access to the contained object, it introduces the overhead of an additional pointer in the class. This pointer is used to reference an integer holding the reference count, which means it can be shared between all references to the wrapped object. However, because the only data held in one of our object variable instances is a single pointer to a reference-counted object, passing and returning object variable instances is efficient.

These object variables are straightforward to use, and can take the place of ordinary pointers or references to objects in many places. Let's assume we're building a simulation which allows a computer user to explore a virtual world. The user can navigate through a number of virtual locations, interacting with objects and other users in the current location. This could be part of an system for designing buildings, or simply an interactive game. A class Agent represents a user, and is always associated with a current location. An instance of agent is created in an initial location, so the Agent constructor takes an ObjVar for the Location class.

class Agent {
public:
        Agent(ObjVar<Location> InitialLoc)
                : CurrentLoc(InitialLoc) {}
        void Enter(ObjVar<Location> NewLoc)
                { CurrentLoc = NewLoc; }
        ObjVar<Location> GetCurrentLoc
                { return CurrentLoc; }
private
        ObjVar<Location> CurrentLoc;
};

Don't forget, while it looks as if whole objects are being passed and assigned in these Agent member functions, the actual instances of Location are held as pointers and wrapped by the ObjVar template. We get the convenience of direct use of variables without having to worry about null pointers being passed or assigned.
The requirements for a Location are somewhat more complex. A Location needs to keep track of all Agents currently present. These will need to be kept on a list of some sort, and the natural solution would usually be to instantiate a templated list class with pointers to the objects in question. Putting pointers to objects in lists can cause problems with ownership: if these objects are contained in more than one list, which is responsible for deleting these objects? The issue with a list of ObjVars is clearer: once an ObjVar is removed from such a list the ObjVar itself is automatically deleted: only if the reference count of the wrapped object consequently becomes zero is the object itself reclaimed. Assuming some standard list accessors on the list template class, here is part of Location's implementation:

class Location {
        public:
                void AgentEnters(ObjVar<Agent> A)
                        { Agents.Add(A); }
                void AgentLeaves(ObjVar<Agent> A);
                        { Agents.Remove(A); }
        private:
                List<ObjVar<Agent> > Agents;
};

How do we add and remove agents from this world? We'll make this the responsibility of an object representing the world as a whole. Here's part of the declaration of a World class:
class World {
        public:
                ...
                ObjVar<Agent> CreateAgent();
                void KillAgent(ObjVar<Agent> A);
                ...
        private:
                List<ObjVar<Location> > Locations;
                List<ObjVar<Agent> > Agents;
                ObjVar<Location> InitialLoc;
};

Creating an agent involves declaring an instance of the Agent object variable type initialised with a new agent and adding her to the world's list of Agents. The world must remember an initial location in which to create agents.
ObjVar<Agent> World::CreateAgent()
{
        // NB dot syntax!
        ASSERT(!InitialLoc.Null());

        ObjVar<Agent> A(new Agent(InitialLoc));
        InitialLoc->AgentEnters(A);

        Agents.Add(A);
        return A;
}

Removing an agent is just as simple, but raises issues of visibility. It is possible that in his travels the Agent has entered into relationships with other objects. Ideally, we deal with this by managing at some level each and every association from the agent's point of view. This could be done using a notification mechanism, or might involve the individual agent remembering each of these associations. Conversely we can rely on the object variable itself to defer actual deletion of the Agent object until no more references exist. This implies that an Agent should enter a 'zombie' state, which can be checked on access.
void World::RemoveAgent(ObjVar<Agent> A)
{
        Agents.Remove(A);
        A->GetCurrentLoc()->AgentLeaves(A);
        A->Kill();
}

// ... and, in agent.cpp ...
void Agent::Kill()
{
        // Set current location to Nul - NB dot syntax!
        CurrentLoc.SetNull();
        // Set current state to zombie
        m_bZombie = true;
}

If this were done using pointers rather than object variables, we would have no choice but to delete the Agent at this stage. If we had missed updating some of the objects which might have referenced this Agent, we would be left in a situation in which future access to the just-deleted agent would be possible, with all-too-familiar results.
There are a cople of drawbacks to this technique. One problem arises from the interpretation of this. When an agent enters a location, it would be convenient for the agent object itself to notify the old and new locations that it has left, as

Page : << Previous 3  Next >>