Monday, May 16, 2005

Using Interfaces in C++ (II)

In the previous post I wrote about a technique that, with the help of a few simple macros, allows using interfaces in C++ code, as you can do in Java or C#. Reader mschaef raises a good point in his comment, which I respond here. A Microsoft extension introduced in VS7 is analyzed here too.

The main point in mschaef's comment is that since the macros don't enforce anything, and don't significantly improve readability, why bother with them at all. Well, if you look closely at the DeclareInterface and DeclareBasedInterface macros, you’ll note there is at least something being enforced there: every class implementing an interface will have a virtual destructor. You may or may not think this is important, but there are cases when the lack of a virtual destructor will cause problems. Consider the following code, for instance:

DeclareInterface(IBar)
public:
virtual LPCTSTR GetName() const = 0;
virtual void SetName(LPCTSTR name) = 0;
EndInterface
 
class Foo : implements IBar
{
// Internal data
private:
char* m_pName;
 
// Construction & Destruction
public:
Foo()
{
m_pName = NULL;
}
 
~Foo()
{
ReleaseName();
}
 
// Helpers
protected:
void ReleaseName()
{
if (m_pName != NULL)
free(m_pName);
}
 
// IBar implementation
public:
virtual const char* GetName() const;
{
return m_pName
}
 
virtual void SetName(const char* name);
{
ReleaseName();
m_pName = _strdup(name);
}
};
 
class BarFactory
{
public:
enum BarType {Faa, Fee, Fii, Foo, Fuu};
 
static IBar CreateNewBar(BarType barType)
{
switch (barType)
{
default:
case Faa:
return new Faa;
case Fee:
return new Fee;
case Fii:
return new Fii;
case Foo:
return new Foo;
case Fuu:
return new Fuu;
}
}
};

There’s a factory to which you can ask to create an IBar implementation, depending on a BarType parameter. After using it, you are expected to delete the object; so far so good. Now consider how this is used in the main function of some application:

int main()
{
IBar* pBar = BarFactory::CreateBar(Foo);
 
pBar->SetName("MyFooBar");
// Use pBar as much as you want, and then
// ...
 
// just delete it when it's no longer needed
delete pBar;
}

What happens at the “delete pBar” line, depends on whether the actual class of that object has a virtual destructor or not. If Foo doesn’t have a virtual destructor, the compiler will only generate a call to IBar’s implicit empty destructor, the destructor for Foo won’t be called, and thus you’ll have a memory leak. The virtual destructors in the interface declaration macros are there in order to avoid this situation; they ensure every class implementing an interface will also have a virtual destructor.

Now, if we are going to use DeclareInterface, it kind of makes sense to also use EndInterface to match with it, rather than a closing brace with no apparent opening one to match. The need for the Interface and implements macros, which resolve to class and public, respectively, could be objected as superfluous, but I find them to better express the actual intent of the code. If I write “Foo : public IBar” you can only read some inheritance there and nothing else. If on the other hand, I write “Foo implements IBar”, you can see it for its actual value and intent: the implementation of the interface concept, and not just any kind of class inheritance. I do find value in that.

To be fair, I’m sure mschaef was actually concerned with the other things these macros do not and cannot enforce: interfaces are expected to have only pure virtual functions and no instance data. But at least, if you write your interfaces using DeclareInterface and EndInterface, the inclusion of any instance data or non virtual pure function should be easy to spot. In Joel Spolsky’s word, these macros also help by “making wrong code look wrong.”

C++ Interfaces support in VS7


The folks at Microsoft must have felt the same need to enforce those restrictions on classes used as interfaces, judging from the introduction in VS7 of the __interface keyword as a new Microsoft extension to the C++ compiler. In the documentation they define a Visual C++ interface as follows:
  • Can inherit from zero or more base interfaces.
  • Cannot inherit from a base class.
  • Can only contain public, pure virtual methods.
  • Cannot contain constructors, destructors, or operators.
  • Cannot contain static methods.
  • Cannot contain data members; properties are allowed.

And they note: “A C++ class or struct could be implemented with these rules, but __interface enforces them.” So, if you are not worried by portability, you could use this extension and have the compiler enforce what needs to be enforced, right? Wrong.

Remember that bit about the need for virtual destructors? __interface does not add a virtual destructor for the implementing class. Couldn’t we just use __interface in the DeclareInterface macro definition, so as to have the best of both worlds? Read again the definition: “cannot contain constructors, destructors, or operators”.

I can understand why MS didn’t see the need for virtual destructors in interfaces. If you look at the samples in the documentation, it’s pretty clear from where they are COMing. They obviously created this extension with COM interfaces in mind and you never use delete with them, because they are count referenced objects, which call delete on themselves when the count reaches zero. So, __interface may be useful for COM interfaces but it’s neither intended nor suitable for the general purpose interfaces we are discussing here.

Labels: ,

1 Comments:

Post a Comment

Links to this post:

Create a Link

<< Back to Main Page