Page #31 (Extending the Interface Functionality)


Managing the Lifetime of an Object

If you recall our TV client code, the Delete method is being called at several different places. The client code has to remember that there was only one object created (using function CreateInstance) and thus only one Delete has to be called, even though the code is dealing with multiple interfaces from the object. For our simple TV client code, this is not a tremendous bur-den. In more complex applications, keeping track of the number of objects created and deleted is cumbersome, as well as prone to errors. For example, consider the case when you have multiple copies of interface pointers for the same object. You use one copy, and thinking that you are done with the object, decide to delete the object. Later on, when you try to use the other copy of the interface, it would result in accessing invalid memory location, resulting in unexpected program behavior.

There is also a semantic problem. The client should only deal with interfaces. The notion of actual object instance and its creation should be as invisible to the client as possible. From a client s point of view, it never creates an instance of the object; it always creates, or somehow obtains, an instance of the interface.


This distinction between an object and an interface is very important. While dealing with an interface, the client knows that there is some object behind this interface. There are perhaps other interfaces that this object supports. However, the client never deals with the object directly. It obtains one interface from another interface, if need be.

Ideally, the client s logic should obtain an interface, use it, then delete it. This is illustrated in Figure 1.8.

Figure 1.8. Client s use of an interface.

Based on this logic, the client code can be simplified to:

 int main(int argc, char* argv[])  {   IGeneral* pVCR = CreateInstance("vcr.dll");    bool bRetVal = UseSVideoIfAvailable(pVCR);    if (false == bRetVal) {     bRetVal = UseVideoIfAvailable(pVCR);    }    if (false == bRetVal) {     // Neither S-Video nor Video      cout << "This VCR does not have the signals we support"         << endl;    }    pVCR->Delete(); // Done with pVCR    return 0;  }  bool UseSVideoIfAvailable(IGeneral* pVCR)  {   IGeneral* pGeneral = pVCR->Probe("svideo");    if (NULL == pGeneral) {     return false;    }    ISVideo* pSVideo = reinterpret_cast<ISVideo*>(pGeneral);    UseSVideo(pSVideo);    pSVideo->Delete();    return true;  } 

As seen, the lifetime management of the actual object has been pushed to the object implementation side. Now the implementation has to deal with the outstanding copies of interfaces for a given object. A simple solution is to maintain a reference count of the outstanding copies of interfaces, as shown here.

 class CVcr : public IVideo, public ISVideo  { public:    ...  public:    // A helper function to increment the reference count    void AddReference();  private:    ...    long m_lRefCount;      // count of outstanding copies of interfaces  }; 

The reference count can be initialized to a value zero in the object construction, as follows:

 CVcr:: CVcr()  {   ...    m_lRefCount = 0;  } 

Method AddReference increments the reference count. Method Delete decrements the reference count. If the reference count reaches zero, it implies there are no more outstanding references to the object. As the object is not needed anymore, it can be deleted, as shown below:

 void CVcr::Delete()  {   if ( ( m_lRefCount) == 0) {     delete this;    }  } 

In this code, the statement delete this is the C++ equivalent of suicide. It is legal to do so, as long as no other calls are made on this instance that would involve using the memory layout of the instance.

Consider the locations where we need to increment the reference count. There are only two such entry points during object creation and during a successful probe:

 IGeneral* CreateVCR(void)  {   CVcr* p = new CVcr;    if (NULL == p)      return p;    p->AddReference();    return static_cast<IVideo*>(p);  }  IGeneral* CVcr::Probe(char* pszType)  {   IGeneral* p = NULL;    if (!stricmp(pszType, "general")) {     p = static_cast<IVideo*>(this);    }else    if (!stricmp(pszType, "video")) {     p = static_cast<IVideo*>(this);    }else    if (!stricmp(pszType, "svideo")) {     p = static_cast<ISVideo*>(this);    }    if (NULL != p) {     AddReference();    }    return p;  } 

With this mechanism in place, the TV client will be able to take the following steps in sequence:

  1. Obtain the IGeneral interface pointer

  2. Obtain the proper video interface pointer

  3. Use the video interface

  4. Delete the interface pointer

  5. Delete the IGeneral interface pointer

The last call to Delete will cause the actual object to get deleted.

By using a simple reference counting mechanism, we reduced the complexity of dealing with multiple interfaces of an object.

There is just one more case of reference counting we haven t dealt with: the case when the client itself makes a copy of the interface, as in the following code snippet:

 IGeneral* pVCR = CreateInstance("vcr.dll");  IGeneral* pVCRCopy = pVCR;  UseVCR(pVCR);  pVCR->Delete();  UseVCR(pVCRCopy);  pVCRCopy->Delete(); 

As far as the object code is concerned, there is only one outstanding reference. Thus, the first call to Delete will delete the object. This will result in a dangling pointer for the second copy, causing unexpected behavior (most likely a crash) when the client tries to use it.

The problem is that the object code never knew that the reference count had to be incremented.

We can easily solve this problem by pushing the responsibility to the client to inform the object that the reference count has to be incremented. All we need to do is make AddReference available to the client by defining it as part of the interface. As you might have guessed, the interface IGeneral is an ideal candidate to add this method to, as shown here:

 class IGeneral  { public:    virtual IGeneral* _stdcall Probe(char* pszType) = 0;    virtual void _stdcall AddReference() = 0;    virtual void _stdcall Delete() = 0;  }; 

Note that AddReference is declared as a pure virtual method. This is in accordance with our rules of defining an interface.

With this mechanism in place, the client code should be revised as:

 IGeneral* pVCR = CreateInstance("vcr.dll");  IGeneral* pVCRCopy = pVCR;  pVCRCopy->AddRererence();  UseVCR(pVCR);  pVCR->Delete();  UseVCR(pVCRCopy);  pVCRCopy->Delete(); 

The client s responsibility in dealing with interface lifetime can be summarized in the following way:

  • If the client obtains an interface pointer from the server, it should call Delete when done using the interface.

  • If the client makes a copy of the interface pointer, it should call AddReference on the copy. It can then call Delete when done using the copy. The client can call AddReference multiple times. However, for each AddReference call, there should be a corresponding Delete call.

At this stage, we should be reasonably comfortable in dealing with multiple interfaces or multiple copies of an interface pointer. However, the drawback of being an expert programmer is that our incessant obsession with efficiency and perfection demands that we consider methods to optimize the code we develop.


COM+ Programming. A Practical Guide Using Visual C++ and ATL
COM+ Programming. A Practical Guide Using Visual C++ and ATL
ISBN: 130886742
Year: 2000
Pages: 129 © 2008-2017.
If you may any questions please contact us: