http://www.developer.com/

Back to article

C++ Idioms in BREW: Better Interfaces


May 2, 2005

Why Static Interfaces in C++

Usually, an interface describes in an abstract way a contract having to be fulfilled by classes implementing that interface. Unfortunately, C++ does more than this when expressing the idea of an interface through Abstract Base Classes (ABC)—all the functions have to be additionally declared pure virtual. This condition is orthogonal to the concept of an interface and at many times superfluous. There are already languages having implemented "interface" without the implicit virtual (C# for example)—and there are some advantages attached to this approach, for example execution speed.

Generally speaking, virtual functions execute slower than the non-virtual counterpart because they need extra memory, branching, and compilers cannot optimize the calls (or to be more precise: can be optimized in very restrictive cases, when the callee's type is known at compile time)—see [1] and [2] for details. On the other hand, an interface (non-ABC) is completely known at compile time, meaning that it's easy to be inlined, for example.

As a rule of a thumb, it is desirable to replace virtual functions, if these are small or called inside a loop. Unfortunately, this happens quite often and might have an impact in a constrained environment like BREW.

There are several approaches.

template<typename ShapeType>
struct Shape
{
   void DrawAShapeType() const
   {
      std::cout << "BaseOp";
      static_cast<const ShapeType &>(*this).Draw();
   }
};
struct Rectangle : public Shape<Rectangle>
{
   void Draw() const
   {
      std::cout << "Rectangle";
   }
};
struct Circle : public Shape<Circle>
{
   void Draw() const
   {
      std::cout << "Circle";
   }
};
template<class S>
void DrawAShape(const S& myShape)
{
   myShape.Draw();
}

template<class S>
void DrawAShapeType(const S& myShape)
{
   myShape.DrawAShapeType();
}
int main (int argc, char * const argv[])
{
   Circle s;
   DrawAShape(s);
   DrawAShapeType(s);
   return 0;
}

One simple solution is to assume an interface, as DrawAShape does. As long as myShape respects the contract (and the contract is enforced at compile-time), DrawAShape behaves polymorphically with respect to all the Shapes. The code above displays a variant based on a "curiously recurring template pattern" (CRTP) where common behavior can be factorized in a common base class.

This technique is definitely easy to use and useful unless there is no need to manipulate Shape dynamically, for example:

Shape& s = Factory::GetShape("Rectangle");
DrawAShape(s);

In this case, a more complicated approach is needed, as presented in [4]. An interface is a non-instantiable type. The original idea was to use a "virtual table" kept hidden inside the interface. The type of the table would be determined from the derived class and kept inside the vtable. The use of static made this version unattractive for BREW development; that's why the following variant embeds the function types directly inside the interface class:

class Interface
{
private:
   typedef  int (*FP1)(void*,  int );
   typedef  int (*FP2)(void*,  const char* );
public:    // interface
      template <class T>
      Interface(T& x)
      : p_(&x),
      pf1_(&functions<T>::foo),
      pf2_(&functions<T>::bar)
   {}
   int foo(int x)
   {
      return pf1_(p_, x);
   }
   int bar(const char* x)
   {
      return pf2_(p_, x);
   }

private:
      template <class T>
      struct functions
   {
      static int foo( void* a, int x)
   {
      return static_cast<T*>(a)->foo(x);
   }
      static int bar( void* a, const char* x)
   {
      return static_cast<T*>(a)->bar(x);
    }
    };
    
    void* p_;
    FP1 pf1_;
    FP2 pf2_;
};

This is similar with this pseudo-construct:

interface Interface
{
      int bar(const char*);
      int foo(int);
}

and can be used like:

struct some_baz
{
   some_baz() : y_(7){}
   int foo(int x) { return x + 1; }
   int bar(char const* s) { return y_; }
private:
   const int y_ ;
};
struct another_baz 
{
   another_baz() : y_(9){}
   int foo(int x) { return x - 1; }
   int bar(char const* s) { return y_; }
private:
   const int y_ ;
};
void demoInterface()
{
   some_baz f;
   another_baz f2;
   Interface p = f;
      //use p;
   p = f2;
//use p
}

Of course, the implementation is far from being elegant, but is extremely efficient. Depending on platform and compiler, the gain might be between 15% and 30%. Based on the observation that an Interface can be composed through inheritance (for example, Interface can be seen as inheriting from an interface wrapping bar and another one wrapping foo) and using macros, you can improve the implementation:

typedef  int (*FP1)(void*,  int );
typedef  int (*FP2)(void*,  const char* );
APPEND_FIRST_INTERFACE_METHOD(Interface,foo,FP1,int);
APPEND_LAST_INTERFACE_METHOD(Interface,1,bar,FP2,int);

Once the function types are defined (note the void* that appears as the first parameter and is not part of the signature but required by the implementation), the functions can be appended. The signature of the macros contains the name of the interface, position of the function (not required for APPEND_FIRST_INTERFACE_METHOD), its name, and return type. This is the list of all the macros defined:

APPEND_FIRST_INTERFACE_METHOD
APPEND_INTERFACE_METHOD
APPEND_LAST_INTERFACE_METHOD
ONE_METHOD_INTERFACE
INHERIT_FROM2    //double interface inheritance
INHERIT_FROM2    //simple interface inheritance

The attached code contains examples as well as the full implementation. As a note of caution, the macro library is very sketchy at this stage and definitely can be improved.

Far from being a natural construct in C++, static interfaces might prove valuable tools when execution speed really matters.

References

  1. Technical Report on C++ Performance: www.research.att.com/~bs/performanceTR.pdf
  2. Todd Veldhuizen: Techniques for Scientific C++: http://osl.iu.edu/~tveldhui/papers/techniques/techniques.html
  3. Linda Rising: Patterns Almanac 2000. Addison-Wesley, 2000
  4. Christopher Diggins: "C++ with Interfaces", C/C++ Users Journal, September 2004

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date