When to delete objects
#include "istring.h"
void main()
{ IString *p;
p =
CreateString("Hello"); if (p)
{ IString2
*p2; IPersist
*p3;
const char*psz =
p->Find("llo"); int
n = p->Length(); if
((p2=(IString2
*)p->Dynamic_cast("IString2")))
char c =
p2->FindAt(3); if
((p3=(IPersist
*)p->Dynamic_cast("IPersist")))
p3->Save("c:\\temp\\str.txt");
p->Delete(); } };
If you look closely at the above code excerpt, the one we ended
up with at the end of the last session, there is a problem. There
are possibly three outstanding references to the same object. We
only need to delete the object once. Currently each interface has a
Delete() function which will delete the object. Which one do we use?
It is not clear. The client certainly has to write code very
carefully to ensure that Delete() only gets called once. Putting
this onus on the client is not a good idea. If the client forgets to
delete we end up with a zombie object and if the client deletes one
too many times we end up with a memory access violation.
We could just put the Delete() function in only one interface,
but which one? It would have to be a 'special' interface implemented
by all objects. If a function is that important to the proper
behavior of an object then it would make more sense to define it as
part of a common base interface from which all other interfaces
derive. We are back at square one - each interface has a Delete()
function!
There is only one sensible solution. Every object must be in
charge of keeping a count of the number of external references that
it has handed out to clients. The obvious place to increment the
count would be in the Dynamic_cast() function as this is where the
object hands out references to itself. The obvious place to
decrement the reference count is where interface references are
invalidated, namely in the Delete() function. This means that every
interface reference held by a client must be invalidated when
finished with, by calling Delete(). The Delete() function must
notice when the reference count goes to zero and delete the object
when it does.
The Delete() method is now badly named so let's change its name
to DestroyPointer().
The header file istring.h now looks like this.
Struct
IString { virtual void
DestroyPointer()=0; virtual void
*Dynamic_cast(const char *psz)=0; virtual void
DuplicatePointer()=0; virtual const
char*Find(const char *psz)=0; virtual int
Length()=0; };
struct
IPersist { virtual void
DestroyPointer()=0; virtual void
*Dynamic_cast(const char *psz)=0; virtual void
DuplicatePointer()=0; virtual void Save(const
char *pszFile)=0; virtual void Load(const char
*pszFile)=0; };
Our slightly changed implementation class now looks like
this.
Header file mystring.h.
#include "istring.h"
class CMyString : public
IString2, public IPersist { private:
char *m_psz; long
m_refcount; public: CMyString(const char *
psz); ~CMyString(); void
DestroyPointer(); void *Dynamic_cast(const
char *); const char*Find(const char
*psz); int Length();
char FindAt(int index); void Save(const char
*pszFile); void Load(const char
*pszFile); };
Source file mystring.cpp.
#include "mystring.h"
CMystring::CMyString(const char *
psz) : m_psz( new char[psz ? strlen(psz)+1 :1]), m_refcount(0)
{ if ( psz
)
strcpy(m_psz,psz);
else m_psz[0] =
0; }
void
*CMyString::Dynamic_cast(const char *psz) {
void *p = NULL; if
(strcmp(psz,"IString")==0)
p = static_cast<IString *>(this);
else if
(strcmp(psz,"IString2")==0)
p = static_cast<IString2 *>(this);
else if
(strcmp(psz,"IPersist")==0)
p = static_cast<IPersist *>(this); if
(NULL!=p)
m_refcount++; return p; }
void CMyString::DestroyPointer()
{ if
(0<m_refcount)
m_refcount--; if
(0==m_refcount) delete
this; }
Our slightly changed client now looks like this.
Source file stringclient.cpp.
#include "istring.h"
void main()
{ IString *p;
p =
CreateString("Hello"); if (p)
{ IString2
*p2; IPersist
*p3;
const char*psz =
p->Find("llo"); int
n = p->Length(); if
((p2=(IString2 *)p->Dynamic_cast("IString2")))
{
char c =
p2->FindAt(3);
p2->DestroyPointer();
} if ((p3=(IPersist
*)p->Dynamic_cast("IPersist")))
{
p3->Save("c:\\temp\\str.txt");
p3->DestroyPointer();
}
p->DestroyPointer(); } };
When references get duplicated
There is still one problem here. What if the client artificially
generates a duplicate reference to the object without the object
knowing? Consider the following code in the client.
Source file stringclient.cpp.
#include "istring.h"
void main()
{ IString *p;
p =
CreateString("Hello"); if (p)
{ IString2
*p2; IString
*p4; IPersist
*p3,*p5;
const char*psz =
p->Find("llo"); int
n = p->Length(); if
((p2=(IString2 *)p->Dynamic_cast("IString2")))
{
char c =
p2->FindAt(3);
p4 = (IString
*)p2;
if (p4) {
const char*psz =
p4->Find("Hell");
p4->DestroyPointer();
}
p2->Delete();
} if ((p3=(IPersist
*)p->Dynamic_cast("IPersist")))
{
p3->Save("c:\\temp\\str.txt");
p5=p3;
if (p5) {
p5->Load("c:\\temp\\str2.txt");
p5->DestroyPointer();
}
p3->DestroyPointer();
}
p->DestroyPointer();
} };
The client has followed the rules and called DestroyPointer()
through every interface pointer when it is finished with. However,
the client has 5 references to the object but only 3 of them were
given out by the object itself. The client generated the extra
references without the object knowing. The object has to
know. Otherwise the object will delete itself too early while there
are still outstanding references. The client could call
Dynamic_cast() through an interface to get the same interface back.
This seems to be overkill. A better solution is to include a
DuplicatePointer() method in every interface that will simply
increment the object reference count.
So our implementation changes slightly again.
Header file istring.h.
Struct
IString { virtual void
DestroyPointer()=0; virtual void
*Dynamic_cast(const char *psz)=0; virtual void
DuplicatePointer()=0; virtual const
char*Find(const char *psz)=0; virtual int
Length()=0; };
Struct IString2 : public
IString { virtual char FindAt(int
index)=0; };
struct
IPersist { virtual void
DestroyPointer()=0; virtual void
*Dynamic_cast(const char *psz)=0; virtual void
DuplicatePointer()=0; virtual void Save(const
char *pszFile)=0; virtual void Load(const char
*pszFile)=0; };
Header file mystring.h.
#include "istring.h"
class CMyString : public
IString2, public IPersist { private:
char *m_psz; long
m_refcount; public: CMyString(const char *
psz); ~CMyString(); void
DestroyPointer(); void *Dynamic_cast(const
char *); void
DuplicatePointer(); const char*Find(const char
*psz); int Length();
char FindAt(int index); void Save(const char
*pszFile); void Load(const char
*pszFile); };
Source file mystring.cpp.
#include "mystring.h"
void
*CMyString::Dynamic_cast(const char *psz){ if
(strcmp(psz,"IString")==0)
{ IString *p =
static_cast<IString *>(this);
if
(NULL!=p)
p->DuplicatePointer();
return (void *)p; }
else if (strcmp(psz,"IString2")==0)
{ IString2 *p =
static_cast<IString2 *>(this);
if
(NULL!=p)
p->DuplicatePointer();
return (void *)p; }
else if (strcmp(psz,"IPersist")==0)
{ IPersist *p =
static_cast<IPersist *>(this);
if
(NULL!=p)
p->DuplicatePointer();
return (void *)p; }
return NULL; }
void
CMyString::DuplicatePointer() {
m_refcount++; }
Now the client code looks like this.
Source file stringclient.cpp.
#include "istring.h"
void main()
{ IString *p;
p =
CreateString("Hello"); if (p)
{ IString2
*p2; IString
*p4; IPersist
*p3,*p5;
const char*psz =
p->Find("llo"); int
n = p->Length(); if
((p2=(IString2 *)p->Dynamic_cast("IString2")))
{
char c =
p2->FindAt(3);
p4 = (IString
*)p2;
if (p4) {
p4->DuplicatePointer();
const char*psz =
p4->Find("Hell");
p4->DestroyPointer();
}
p2->DestroyPointer();
} if ((p3=(IPersist
*)p->Dynamic_cast("IPersist")))
{
p3->Save("c:\\temp\\str.txt");
p5=p3;
if (p5) {
p5->DuplicatePointer();
p5->Load("c:\\temp\\str2.txt");
p5->DestroyPointer();
}
p3->DestroyPointer();
}
p->DestroyPointer();
} };
Now the object knows about all outstanding references will delete
itself at the right time.
Object creation revisited
Note that when the object is created its reference count is zero
- there are no outstanding references at this point. The first
reference is given out by the out-of-band object creation function.
It must pass back a counted interface reference. At this point we
could amend the creation function to pass back a counted, type-safe
reference to any interface that is supported by the object, at the
time it is created. This is how it would look.
Header file istring.h.
extern "C"
_declspec(dllexport)void *CreateString(const char *psz, const char
*pszinterface); typedef IString * (*PfnCreateString)(const char
*psz, const char *pszinterface);
Source file mystring.cpp.
extern "C" void
*CreateString(const char *psz, const char *pszinterface)
{ void *pret = NULL;
CMyString *p = new CMyString(psz); if
(NULL!=p) { pret=
p->Dynamic_cast(pszinterface);
if
(NULL==pret)
delete p; } return
pret;}
Source file stringclient.cpp.
#include "istring.h"
void main()
{ IString2 *p; HANDLE h
= LoadLibrary("c:\\temp\\mystring.dll"); if
(NULL!=h) {
PfnCreateString pfn =
(PfnCreateString)GetProcAddress(h,"CreateString");
if (pfn)
{
p = (IString2 *)pfn("Hello","IString2");
if
(p) {
char c =
p->FindAt(3);
p->DestroyPointer();
}
} // Be careful about
calling FreeLibrary.
// It will summarily dismiss the DLL from
memory. // You may
still have objects in
memory. //
} }
If you would like to write some code to try this out then click here.
Next
we will look at developing an extensible base for all
interfaces.

|