COM+ Programming A Practical Guide Using Visual C++ and ATL [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

COM+ Programming A Practical Guide Using Visual C++ and ATL [Electronic resources] - نسخه متنی

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید

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 clients 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 clients logic should obtain an interface, use it, then delete it. This is illustrated in Figure 1.8.



Figure 1.8. Clients 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 pointsduring 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:


Obtain the IGeneral interface pointer

Obtain the proper video interface pointer

Use the video interface

Delete the interface pointer

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 havent 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 clients 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.

/ 125