December 19, 2014
Hot Topics:

C++ Idioms in BREW: Better Interfaces

  • May 2, 2005
  • By Radu Braniste
  • Send Email »
  • More Articles »

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_;
};




Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel