C++ with Interfaces

Christopher looks at how abstract base classes differ from interfaces and how you can implement efficient interfaces in C++ without virtual functions.


September 01, 2004
URL:http://www.drdobbs.com/c-with-interfaces/184401848

September, 2004: C++ with Interfaces

Christopher Diggins is a freelance computer programmer and developer of Heron, a modern general-purpose open-source language inspired by C++, Pascal, and Java. He can be contacted at http://www.heron-language.com/.


While interface constructs are a commonplace feature in many newer languages (Java, Delphi, C#, Heron, and D, for instance), they're not in C++. The most well-known and advocated approach to implement interface-like functionality in C++ is through the use of abstract base classes (ABCs). In this article, I examine how interfaces differ from ABCs, and how you can implement efficient interfaces in C++ without virtual functions. In the process, I present HeronFront, a code generator for C++ that generates interface-style types from a file containing interface declarations.

What Are Interfaces?

Interfaces are noninstantiable types that contain only function declarations. A variable with an interface type acts as a reference to any object that implements that particular interface. To implement an interface, an object must supply definitions for all of the functions that make up the interface. In many languages, a class declares its intent to implement an interface explicitly, but this is not necessarily the case.

Interfaces resemble ABCs in many ways and as a result are commonly confused. There is, however, one significant difference—a type that implements an interface is not necessarily an implicit statement of intention for the interface functions to be virtual. That is a side effect of using ABCs to emulate interfaces and is surprising from a straightforward interpretation of what an interface is. Therefore, using an ABC to emulate an interface is incorrect, at least from a theoretical standpoint. But does this make a difference from a practical standpoint?

Comparing ABCs to Interfaces

When using ABCs to emulate interfaces, we unintentionally make functions virtual, which leads to an unintentional design change and to unnecessary performance penalties. The design change occurs when you consider code such as this:

interface IFuBar {
  contract:
    void FuBar();
};

struct BaseFuBar {
  void FuBar() { std::cout << 
    "BaseFuBar"; };
};

struct DerivedFuBar : public BaseFuBar {
  void FuBar() { std::cout << 
    "DerivedFuBar"; };
};

main() {
  DerivedFuBar d;
  BaseFuBar& b = d;
  IFuBar i = b;
  i.FuBar(); // outputs BaseFuBar
};

The common way to approximate this design in standard C++ is:

struct AbcFuBar {
  virtual void FuBar() = 0;
};

struct BaseFuBar : public AbcFuBar {
  void FuBar() { std::cout << 
    "BaseFuBar"; };
};

struct DerivedFuBar : public BaseFuBar {
  void FuBar() { std::cout << 
    "DerivedFuBar"; };
};

main() {
  DerivedFuBar d;
  BaseFuBar& b = d;
  AbcFuBar& a = b;
  a.FuBar(); // output DerivedFuBar
};

This is not by any means a bad or incorrect design, but it is not what you would expect from an interface. However, it is the only way that you can easily have runtime polymorphic variables in C++. Notice that I say "easily," since there exist alternatives with excellent performance, but are rather verbose.

Some programmers view the difference as somewhat pedagogical, but there are potentially significant performance penalties in terms of execution speed and memory usage for having superfluous virtual functions in a class.

Emulating Interfaces: Disadvantages

One of the major problems of using multiple inheritance of ABCs is vtable pointer bloat, which occurs in most implementations of C++. The problem is this: Each time a class has in its inheritance hierarchy a different branch with a virtual function, another vtable pointer is added to the class. Typically, this is not viewed as a debilitating problem because inheritance is meant to model is-a type object relationships, for which single inheritance is often appropriate. Interfaces, though, are often intended to model behaves-like or looks-like relationships, which can lead to much broader inheritance trees (assuming you use ABCs), which makes the problem much more significant.

Another disadvantage of virtual functions is that they execute slower. This stems mostly from the inability of compilers to inline virtual functions in many situations, which is one of the most common and effective forms of optimizations a compiler can perform.

Hand-Coding Interface Types

Interfaces can still be implemented in C++ without virtual functions through an interface class that contains a pointer to a function pointer table and an object.

Listing 1 was posted on comp.std.c++ by David Abrahams of Boost Consulting (http://www.boost-consulting.com/) and is an excellent example of how to code an interface type. That code is presented here with only a small change to the visibility specifiers in order to make it compatible with my Borland compiler. This code is very efficient, but as you can see, it is quite verbose and perhaps a bit tricky to write. The technique, though, is not as complicated as it might seem at first. The baz class contains two member variables—_m_a, which is a pointer to an object and _m_t, which is a pointer to the function dispatch table. The function dispatch table is constructed using a parameterized constructor. The interface then routes any call to a member function through the static function table, which in turn routes the call to the appropriate object in a type-safe manner.

One limitation of this technique is that it doesn't work well as a parameterized type, but this can be easily overcome. Another method of implementing interfaces was also proposed on comp.std.c++ by Dave Harris, which managed to isolate some of the complexity into an interface class.

HeronFront: A Code Generator for Interfaces

For some people (myself included), writing interface-style classes is too verbose and error prone. This leads naturally to adding an extra code-generation step to the compilation process. Consequently, I wrote HeronFront (http://www.heron-language.com/heronfront.html and http://www.cuj.com/code/) as a free, open-source program to do just that. HeronFront simply generates reference classes from interface declarations.

HeronFront interface declarations take this form:

[template <template_parameter_list>]
interface interface_name [: interface_inheritance_list]
{
  function_declaration_list
};

Interface classes generated by HeronFront support this functionality:

template<typename T> interface_name& operator=(T& x);
template<> interface_name& operator=(IFuBar& x);
template<typename T> interface_name(const T& x);
template<> interface_name(const interface_name& x);

These interface classes behave much like references to the object they are constructed with, but are reassignable.

Interfaces Versus ABCs: An Example

To demonstrate the memory-bloat problem and performance penalties of using ABCs to emulate interfaces, I present an example that compares using HeronFront interfaces to a design written using ABCs. Consider the example of a circle class from an imaginary drawing program. Listing 2 is the file shapes.hfront, which translates with HeronFront to shapes.hpp in a prebuild step in the makefile. Listing 3, on the other hand, is the file heron-test.cpp, which compares the interface method with the abc method. The program outputs Example 1 on my machine (an Intel Celeron 1.8-GHz with 256-MB RAM running Windows XP) when compiled with Borland C++ 5.5.1 Compiler for Windows and the -O2 (speed optimization) command-line switch is activated.

From this output, you can see that the Circle type is less than half the size of the NaiveCircle type and it is also able to execute MoveBy calls in half the time. Timing results obviously vary from compiler to compiler, and conditions where ABCs perform more quickly than interfaces still exist. In general and for most compilers, however, the nonvirtual versions of the functions out-perform the virtual versions.

Conclusion

Interface types can provide an alternative method of nonintrusive polymorphism, which, for certain scenarios, are more efficient and appropriate than traditional abstract base-class inheritance design patterns. Interfaces are a powerful concept that have applicability in a wide range of problem domains and exploration is being done as to their usefulness in domains, ranging from test-driven design and extreme programming to aspect-oriented programming and design by contract, among others.

Acknowledgments

Thanks to David Abrahams of Boost Consulting for his excellent interface code.

September, 2004: C++ with Interfaces

Example 1: Program output.

sizeof(IShape) = 8
timing 5000000 calls to IShape::MoveBy() ... time elapsed (msec): 187
sizeof(AbcShape&) = 16
timing 5000000 calls to AbcShape&::MoveBy() ... time elapsed (msec): 485
sizeof(IMoveable) = 8
timing 5000000 calls to IMoveable::MoveBy() ... time elapsed (msec): 203
sizeof(AbcMoveable&) = 4
timing 5000000 calls to AbcMoveable&::MoveBy() ... time elapsed (msec): 484
sizeof(Circle&) = 12
timing 5000000 calls to Circle&::MoveBy() ... time elapsed (msec): 156
sizeof(NaiveCircle&) = 28
timing 5000000 calls to NaiveCircle&::MoveBy() ... time elapsed (msec): 469
sizeof(Circle) = 12
timing 5000000 calls to Circle::MoveBy() ... time elapsed (msec): 79
sizeof(NaiveCircle) = 28
timing 5000000 calls to NaiveCircle::MoveBy() ... time elapsed (msec): 344

September, 2004: C++ with Interfaces

Listing 1

// a baz "interface"
class baz
{
 public:
    // forward declarations
    template <class T>
    struct functions;
    // interface calls
    template <class T>
    baz(T& x)
      : _m_a(&x), _m_t(&functions<T>::table)
    {}
    int foo(int x)
    { return _m_t->foo(const_cast<void*>(_m_a), x); }
    int bar(char const* x)
    { return _m_t->bar(const_cast<void*>(_m_a), x); }
    // Function table type for the baz interface
    struct table
    {
        int (*foo)(void*, int x);
        int (*bar)(void*, char const*);
    };
    // For a given referenced type T, generates functions for the
    // function table and a static instance of the table.
    template <class T>
    struct functions
    {
        static baz::table const table;
        static int foo(void* p, int x)
        { return static_cast<T*>(p)->foo(x); }
        static int bar(void* p, char const* x)
        { return static_cast<T*>(p)->bar(x); }
    };
 private:
    void const* _m_a;
    table const* _m_t;
};
template <class T>
baz::table const
baz::functions<T>::table = {
    &baz::functions<T>::foo
  , &baz::functions<T>::bar
};
#include <cstring>
#include <cstdio>

struct some_baz
{
    int foo(int x) { return x + 1; }
    int bar(char const* s) { return std::strlen(s); }
};
struct another_baz
{
    int foo(int x) { return x - 1; }
    int bar(char const* s) { return -std::strlen(s); }
};
int main()
{
    some_baz f;
    another_baz f2;
    baz p = f;
    std::printf("p.foo(3) = %d\n", p.foo(3));
    std::printf("p.bar(\"hi\") = %d\n", p.bar("hi"));
    p = f2;
    std::printf("p.foo(3) = %d\n", p.foo(3));
    std::printf("p.bar(\"hi\") = %d\n", p.bar("hi"));
}

September, 2004: C++ with Interfaces

Listing 2

// HeronFront source file : shapes.hfront
// translated into target : shapes.hpp

interface IDrawable {
  void Draw() const;
};
interface IPostion {
  Point GetPos() const;
};
interface IMoveable {
  void MoveTo(const Point& x);
  void MoveBy(const Point& x);
};
interface ISizeable {
  void SetSize(int x);
  int GetSize() const;
};
interface IShape : IPositon, IDrawable, IMoveable, ISizeable {
};

September, 2004: C++ with Interfaces

Listing 3

#include <iostream>
#include <vector>
#include <list>
using namespace std;
#include "timeit.hpp"

struct Point {
  int mx, my;
  Point() { mx = 0; my = 0; };
  Point(int x, int y) { mx = x; my = y; };
  Point Plus(const Point& x) { return Point(x.mx + mx, x.my + my); };
  void Draw() const { cout << "x: " << mx << " y: " << my; };
  void MoveTo(const Point& x) { mx = x.mx; my = x.my;  };
};
#include "shapes.hpp"
struct Circle {
  Circle() { mSize = 0; };
  void Draw() const { /* noop */ };
  void MoveTo(const Point& x) { mCenter.MoveTo(x); Draw(); };
  void MoveBy(const Point& x) { MoveTo(GetPos().Plus(x)); };
  Point GetPos() const { return mCenter; };
  void SetSize(int x) { mSize = x; };
  int GetSize() const { return mSize; };
  Point mCenter;
  int mSize;
};
struct AbcDrawable {
  virtual void Draw() const = 0;
};
struct AbcPosition {
  virtual Point GetPos() const = 0;
};
struct AbcMoveable {
  virtual void MoveTo(const Point& x) = 0;
  virtual void MoveBy(const Point& x) = 0;
};
struct AbcSizeable {
  virtual void SetSize(int x) = 0;
  virtual int GetSize() const = 0;
};
struct AbcShape : public AbcDrawable, public AbcPosition, 
                                 public AbcMoveable, public AbcSizeable {
};
struct NaiveCircle : public AbcShape {
  NaiveCircle() { mSize = 0; };
  void Draw() const { /* noop */ };
  void MoveTo(const Point& x) { mCenter.MoveTo(x); Draw(); };
  void MoveBy(const Point& x) { MoveTo(GetPos().Plus(x)); };
  Point GetPos() const { return mCenter; };
  void SetSize(int x) { mSize = x; };
  int GetSize() const { return mSize; };
  Point mCenter;
  int mSize;
};
template<typename T> void RunTest(T x, const char* s) {
  const int ITERS = 5000000;
  Point pt(0, 0);
  cout << "sizeof(" << s << ") = " << sizeof(T) << endl;
  cout << "timing " << ITERS << " calls to " << s << "::MoveBy() ... ";
  TimeIt t;
  x.MoveTo(pt);
  for (int i=0; i < ITERS; i++)  {
    x.MoveBy(Point(i, i));
  }
};
int main()
{
  Circle c1;
  NaiveCircle c2;

  RunTest<IShape>(c1, "IShape");
  RunTest<AbcShape&>(c2, "AbcShape&");
  RunTest<IMoveable>(c1, "IMoveable");
  RunTest<AbcMoveable&>(c2, "AbcMoveable&");
  RunTest<Circle&>(c1, "Circle&");
  RunTest<NaiveCircle&>(c2, "NaiveCircle&");
  RunTest<Circle>(c1, "Circle");
  RunTest<NaiveCircle>(c2, "NaiveCircle");

  cin.get();
  return 0;
}

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.