Thursday, May 12, 2005

Using Interfaces in C++ (I)

The distinction between classes and interfaces is a powerful language feature present both in Java and C# (and probably many other languages) but not in C++. For years, our team has been using a "methodological" implementation of the interface concept for C++. Starting in VS7, there's also an MS extension pointing in the same way, allowing the compiler to enforce most of the defining characteristics of an interface, and of course, managed extensions for C++ supports the definition and implementation of .NET interfaces too. However there are some subtle and not so subtle differences between each of these mechanisms that you should be aware of.

Many years ago, around November 1999, I defined a way to declare interfaces for C++, and to make classes implement them, just by using a few macros and making sure some basic rules were followed. I should make clear I don't claim to be the "inventor" of this technique or anything like that. Although it was independently developed, mostly with the invaluable advice of Roberto Lublinerman, I later found many internet articles, which described more or less the same ideas, and at least some of them were dated way before we had even came to think about it.

More recently, I wrote about this technique in a rather lengthy usenet post, but let me summarize it here.

First, some macros are defined in a header file, which you'll probably want to include in your precompiled headers:

#define Interface class
 
#define DeclareInterface(name) class name \
{ \
public: \
virtual ~name() {}
 
#define DeclareBasedInterface(name, base) class name \
: public base \
{ \
public: \
virtual ~name() {}
 
#define EndInterface };
 
#define implements public

Using these macros, you can declare an interface in the following way:

DeclareInterface(IBar)
public:
virtual int GetBarData() const = 0;
virtual void SetBarData(int nData) = 0;
EndInterface

Then you can declare a class that implements this interface with something like:

class Foo : public BasicFoo, implements IBar
{
// Construction & Destruction
public:
Foo(int x) : BasicFoo(x)
{
}
 
~Foo();
 
// IBar implementation
public:
virtual int GetBarData() const
{
// stuff...
}
 
virtual void SetBarData(int nData)
{
// stuff...
}
};


Easy, isn't it? Without much effort you are now able to use interfaces in C++. However, as they aren't directly supported in the language, you are supposed to follow some rules, which can't be automatically enforced at compilation time. After all, all the compiler can see is the use of plain old multiple inheritance. So, here are the rules:
  • When declaring a class, use the first base class for "structural" inheritance (the "is a" relationship) if there is one, as you normally do (eg: CFrameWnd derives from CWnd, CBitmapButton derives from CButton, YourDialog derives from CDialog, and so on). This is specially important if you are deriving from MFC classes; declaring them as the first base class avoids breaking MFC's RuntimeClass mechanism.
  • Use additional base classes for interface implementation, as many as you want or need (eg: class Foo : public BasicFoo, implements IBar, implements IOther, implements IWhatever, ...)
  • Do not declare any member variables inside interfaces. Interfaces are intended to express behaviour, not data. Besides, this helps avoid some problems if you were to use multiple "structural" inheritance and happened to be deriving more than once from the same interface.
  • Declare all member functions in interfaces as virtual pure (ie: with "= 0"). This ensures every instantiable class that declares to implement an interface does it for all its functions. It's OK to partially implement an interface in an abstract class (in fact it will be abstract anyway if you do so) as long as you implement the remaining functions in the derived classes you actually intend to instantiate. Since the interfaces offer no "basic" implementation, you need to be sure everyone that receives a pointer to some interface will be able to call any of its members; declaring all your interface members as virtual pure will enforce this at compile time.
  • Do not derive your interfaces from anything but other interfaces. You can use the DeclareBasedInterface() macro for that. Normal classes can choose to implement the basic interface or the derived (extended) one; the latter of course means implementing both.
  • Assigning a pointer to a class that implements some interface to a pointer to that interface requires no casting, as you will be actually casting to a base class. Doing it the other way though (from an interface pointer to a pointer to the class implementing it), will require an explicit cast, as you will be casting to a derived class. Since you will be in fact using multiple inheritance (even if for virtually every other practical need you can think of it as single inheritance plus interace implementations), these casts can't be done the "old" way, because they may need different actual memory values. However, enabling Run-Time Type Information (/GR compiler option) and using dynamic casts works fine and it's of course safer anyway.
  • In fact the use of dynamic_cast will give you a way to ask any object or interface whether it implements a given interface or not.
  • You need to be careful to avoid name conflicts between functions in different interfaces, as it isn't easy to detect and resolve those conflicts if you have a class implementing both.

Labels: ,

10 Comments:

Post a Comment

Links to this post:

Create a Link

<< Back to Main Page