JLR on C++ interfaces

| 0 Comments

A while ago I came across JLR's weblog and read his three postings using interfaces in C++ (1, 2, 3). I fired off a quick comment on the first article because I didn't agree with a couple of things he was saying. Jose's response to my comment grew into this posting and, well, now I need to explain my position in more detail.

This is it...

The first point that Jose takes issue with is that I dismiss his use of macros without explaining why. I said this: "Apart from the fact that I dislike the whole idea of using macros to somehow make abstract base classes into interfaces - an abstract base class IS ALREADY an interface in C++." in response to the macros that he proposed that looked like this:

#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
Which he proposes using like this:
DeclareInterface(IBar)
public:   
   virtual int GetBarData() const = 0;   
   virtual void SetBarData(int nData) = 0;
EndInterface

and
class Foo : public BasicFoo, implements IBar

My view is that C++ programmers that understand the language don't need macros that make the language look like something else. The macros above are about as necessary as the ones that Pascal programmers coming to C++ sometimes create; #define begin { and #define end } and all the other 'useful' little macros that 'help' you to not have to learn real C++ syntax. All these things do is generate confusion when you add more C++ programmers to the team (you can be pretty sure that most C++ programmers will not be conversant with your local macro customs, some will just learn them and get on with it and some will argue about it with you...). I also find that once someone starts doing 'clever' things like this with macros it tends to spread. Macros become an acceptable way of solving problems and because they're so 'easy' they often become the preferred way; and I don't think they should be. Macros in C++ should be the last resort. So, personally I wouldn't allow this kind of thing in my code.

That's not really the point of my problem with these particular macros though; my main problem is that Jose is attempting to codify a way of using C++ abstract classes as interfaces in such a way that he loses a lot of flexibility; with these macros there's only one true way to use abstract classes as interfaces... Perhaps that's his intention, but personally I have several different ways that I use abstract interfaces in C++.

Moving on from my swipe at macros in general, Jose picks me up on my assertion that "an abstract base class IS ALREADY an interface in C++". I agree that interface is an overloaded word but I'm comfortable with taking the view that's detailed in section 12.5 of "The C++ Programming Language (2nd Ed)" that "An abstract class in an interface.". I've no problem with Jose's further clarification of 'pure interfaces', but I don't, personally, need the clarification to know what we both mean.

The main purpose of my comment was the way that Jose's macros enforced a public virtual destructor on all of his interfaces. My point was, and still is, that this is only sometimes appropriate. In fact it's only appropriate if the users of your derived class are allowed to delete it though a pointer to the abstract base class. If users of your interface are not allowed to delete the objects then your destructor of your interface should be protected, so that the object can't be deleted through a pointer to the interface and if that's the case then there's no need for it to be virtual. So, the main point of my comment was that abstract base classes should have either a public virtual destructor (if you want users of the class to be able to delete objects) or a protected non virtual destructor (if you don't). See here (guideline #4) for more details.

Jose agrees with me on the above, but he says that his usage pattern for his interfaces is one where he usually wants to be able to delete his objects via his interfaces and to that end he uses his macros to add a "convenient virtual destructor". In sorry but the accessibility of a destructor is not something that should be convenient. It's something that should be appropriate. Either the object can be deleted via a pointer or it can't. There's no convenient solution only a correct solution. Either the destructor is public and virtual or it's protected (or private) and non virtual. Having a macro that provides a convenient way to create an interface with just the loosest of these destruction options means that the user of the macro is less likely to consider what's actually appropriate for their class and instead end up with this default deletion option even if it's not appropriate. When it's not appropriate, a public virtual destructor is a bug.

Jose then describes how COM interfaces are used, and then says that his interfaces aren't COM interfaces. Well, my interfaces aren't COM interfaces either, but more than 50% of the time I don't need to delete objects via their interface. Even if I did need to do that 99% of the time I still wouldn't want to have the 'convenience' of an automatically added public virtual destructor because in 1% of my interfaces that would be a bug. I completely understand where Jose's coming from with his factory methods and decoupling the user of a class from its implementation, etc, etc. But I still disagree with him including a public virtual destructor in his macro. In fact I'd go so far as to say that it's actually only a naive implementation of a factory pattern that allows the clients of the objects that the factory creates to assume that they can simply be deleted using delete. Doing so is almost always limiting; for example, you can't cache objects or reliably demand load implementations from DLLs without having to change all of your clients. I usually find that it's far better to insulate clients from both creation AND destruction of my implementations and that generally means not allowing clients to call delete via an interface.

So, in summary, you should think about what you're doing each time you define an interface and make the appropriate decision for how you define the destructor. There is no 'convenient' decision that doesn't introduce bugs.

Jose wants it to be easy to define his interfaces. I'd say just define them in C++ in the "normal" way. Then, with no macro crud in the way, it's easy for all C++ programmers to know if they do the right thing. If you remove the public virtual destructor then the macros add no value at all.

It's lucky I didn't comment on his mistaken belief that classes should always inherit publicly from their interfaces... ;)

Leave a comment