top header
com as a better c++term

 

home contents introduction com as a better C++ interfaces classes apartments labs lab setup quizzes
 
previousnext

 
Lifecycle management

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.


Top of document