Don Box – Essential COM: Why define the creation operator as a non-member function when the deletion operator is a member function?

  c++, com

Essential COM, Don Box

Chapter 1, Abstract Bases as Binary Interfaces, Page 18

If you do not have a copy of this book, and are curious about Component Object Model (COM) scroll down to the bottom where I give some further background information.

The IFastString interface class is defined as

// ifaststring.h
class IFastString
{
    public:
    virtual void Delete() = 0;
    virtual int Length() const = 0;
    virtual int Find(const char*) const = 0;

};

extern "C"
IFastString* CreateIFastString(const char* psz);

The FastString class is defined as

// faststring.h
#include "ifaststring.h"

class FastString : public IFastString
{
    private:
    const int m_ch;
    char *m_psz;

    public:
    FastString(const char *psz);
    ~FastString();
    void Delete();
    int Length() const;
    int Find(const char *psz) const;
};

Finally, here is the implementation of FastString

// faststring.cpp
#include <string.h>
#include <faststring.h>

IFastString* CreateFastString(const char *psz)
{
    return new FastString(psz);
}

FastString::FastString(const char *psz)
    : m_ch(strlen(psz))
    , m_psz(new char[m_ch + 1])
{
    strcpy(m_psz, psz);
}

FastString::~FastString()
{
    delete [] m_psz;
}

void FastString::Delete()
{
    delete this;
}

int FastString::Length() const
{
    return m_ch;
}

int FastString::Find(const char *psz) const
{
    // find algorithm implementation goes here
}

When I first saw this code it appeared slightly "odd" to me. I am confused as to why the creation function is not defined in a similar way to the delete function.

It would seem to me there are two possible modifications to this code. As far as I can see, both of the following choices would work.

1: The creation function could be made a member function. (?)

I initially thought this would be possible however I now believe this cannot be done because C++ class member functions cannot be defined with extern "C" linkage.

Therefore the choice to define the creation function as a non-member is not completely arbitrary.

2: The deletion function could be made a non-member function. It would then more closely match with the creation function.

// h (interface)
extern "C" // is this needed?
void DeleteIFastString(IFastString *s);

// cpp
void DeleteIFastString(IFastString *s)
{
    delete s;
}

// remove the virtual void Delete() function from both
// IFastString and FastString classes

The only reason I can think for this decision not being taken is that it polutes the global namespace with another non-member function. However on the other hand, having a syntactically similar "deleter" to your "creator" function is arguably desirable.

Is there any other reason why the deleter has been implemented as a member function?

Further Background Info (COM)

The purpose of the ideom in the above example is to solve two problems:

  1. A dynamic library should be designed with a defined interface which does not change.

This is so that the implementation can be changed without requiring modification to client code. Someone can update the "FastString" dynamic library keeping the interfaces constant so that client code can link to a new version of the dynamic library without requiring changes to the client source.

  1. A dynamic libary should be designed with an exposed object size which does not change.

This is so that a changed dynamic library can be shipped to a client and the compiled client code can load the dynamic library code, and functions will access data at the correct offset.

Consider the following: If member variables of an interface class are changed, then the offsets or locations of where those data are stored in memory will change. If an interface class is exposed to client code, then different compiled versions of that interface class will be binary incompatiable.

  1. If the client code is compiled with a different compiler to the compiler used to compile the dynamic libary code, then the dynamic libary should be loadable at runtime by the client and work as expected.

The reason this may not happen is if the compilers used compile code in an incompatiable way. For example, virtual functions have multiple possible implementations, as the C++ standard does not define how polymorphism and virtual functions should be implemented. Different compilers may produce different binary codes which are incompatiable.

Don Box describes this in more precision than I have here over the course of several pages. Hopefully I presented enough information for this to be understandable.

COM solves the above three issues by defining an interface class with no member variables, along with an implementation class the data members and functions of which are completely inaccessible to client code. In addition to this, all functions which touch member data of the implementation class are compiled with the same compiler. (The compiler used to compile the dynamic library.) This means that the functions and data are compiled by the same compiler and are thus binary compatiable. Finally, user accessible functions are defined with external C linkage so that they are compatiable and can be linked with client code compiled with a different compiler.

It is worth noting that the above assumes the archetecture is the same. For example, COM does not solve the problem of x86 code being incompatiable with ARM systems. (Probably obvious but worth mentioning to avoid confusion.)

Source: Windows Questions C++

LEAVE A COMMENT