Programming with Interfaces in C++

Programming with Interfaces in C++

By Lois Goldthwaite

Overload, 8(40):, December 2000


A New Approach

This article grew out of an email exchange concerning an article in Bill Venners's column in the online magazine Javaworld. You can read the full article at www.javaworld.com/ javaworld/jw-12-1998/jw-12-techniques.html , though I will summarise its main points here. Venners also maintains a discussion group for his articles, and the follow-up postings can be found at www.artima.com/flexiblejava/fjf/interfaces/index.html .

The gist of the article - and by all means go read the original, because I do not want to twist Bill's words - is that Java, with its concept of interfaces as a major language feature, leads to better design and implementation than C++.

Interface vs Implementation

In the beginning, says Venners, he looked on Java interface s as a halfway version of multiple inheritance. Although he was sure the Java solution was superior, he could not really say why:

"Prior to the advent of Java, I spent five years programming in C++, and in all that time I had never once used multiple inheritance. Multiple inheritance wasn't against my religion exactly, I just never encountered a C++ design situation where I felt it made sense. When I started working with Java, what first jumped out at me about interfaces was how often they were useful to me. In contrast to multiple inheritance in C++, which in five years I never used, I was using Java's interfaces all the time.

"So given how often I found interfaces useful when I began working with Java, I knew something was going on. But what, exactly? Could Java's interface be solving an inherent problem in traditional multiple inheritance? Was multiple inheritance of interface somehow intrinsically better than plain, old multiple inheritance?"

The article then launches into a detailed explanation of the 'diamond problem', which arises when two classes inherit from the same base class, and then a fourth class inherits from both of the derived classes. The strange hierarchy below relates to the movie Jurassic Park, where frog DNA was used to fill out incomplete dinosaur DNA:

abstract class Animal 
{
  abstract void talk();
}
class Frog extends Animal 
{
  void talk() { System.out.println("Ribit, ribit."); }
}
class Dinosaur extends Animal 
{
  void talk() 
  { 
    System.out.println(
     "Oh I'm a dinosaur and I'm OK...");
  }
}
// (This won't compile, of course -
// Java only supports single 
// inheritance.)
class Frogosaur extends Frog, Dinosaur {
}

The diamond problem rears its ugly head when someone tries to invoke talk() on a Frogosaur object from an Animal reference, as in:

Animal animal = new Frogosaur();
animal.talk();  //[ambiguous call - lg]

This was the first argument that annoyed me. I would agree that multiple inheritance in C++, even with completely abstract base classes to avoid the "burden" of multiple inheritance of implementation, is a feature best used sparingly. But I think Java devotees make too much of the dreaded diamond inheritance problem. Multiple inheritance is an elegant expression of a situation where a class logically inherits features from two different domains. The diamond situation rarely arises, and when it does, it should prompt a re-think of the design. But too many Java-centric articles imply that the mere existence of multiple inheritance in C++ must inevitably lead to clumsy design and kludgy implementations.

Polymorphism is Key

But to get back to the Venners article, he goes on to describe what he sees as the real advantage of interfaces in Java:

"As time went by I began to believe that the key insight into the interface was not so much about multiple inheritance as it was about polymorphism ... Java's interface gives you more polymorphism than you can get with singly inherited families of classes, without the 'burden' of multiple inheritance of implementation."

Polymorphism, of course, refers to the ability of any object of a derived class to function as a substitute for its parent class, and do so in an appropriate way. All Animal s have certain common behaviours, although they may perform them differently - a dog will bark, a cat will meow, a hamster will squeak, and so forth. The appropriate behaviour is determined at runtime through the magic of dynamic binding, based on the actual type of the object involved.

interface Talkative 
{
  void talk();
}
abstract class Animal 
  implements Talkative 
{
  abstract public void talk();
}

class Dog extends Animal 
{
  public void talk() 
  {   
    System.out.println("Woof!"); 
  }
}
class Cat extends Animal 
{
  public void talk() 
  { 
    System.out.println("Meow.");
  }
}
class Interrogator 
{
  static void 
  makeItTalk(Talkative subject) 
  { 
    subject.talk(); 
  }
}

"Given this set of classes and interfaces, later you can add a new class to a completely different family of classes and still pass instances of the new class to makeItTalk() ."

class CuckooClock implements Talkative 
{
  public void talk() 
  { 
    System.out.println("Cuckoo,cuckoo!");
  }
}
class Example4 {
  public static void main(String[] args)
  {
    CuckooClock cc = new CuckooClock();
    Interrogator.makeItTalk(cc);
  }
}

"With single inheritance only, you'd either have to somehow fit CuckooClock into the Animal family, or not use polymorphism. With interfaces, any class in any family can implement Talkative and be passed to makeItTalk() . This is why I say interfaces give you more polymorphism than you can get with singly inherited families of classes."

In later messages to the discussion group, Venners adds that his experience in using Java interfaces would now lead him to make greater use of abstract base classes, if he were writing C++. Sigh. So many authors refer to C++ as it was more than five years ago. They overlook that modern C++ has an excellent mechanism for providing non-inheritance-based polymorphism, through the use of templates.

Templates as Interfaces

The C++ idiom to express the Talkative interface discussed in Venners's article would look something like this:

template <class T>
class Talkative 
{
  T const & t;
public:
  Talkative(T const & obj) : t(obj) { }
  void talk() const { t.talk(); }
};

This template enables any class which defines a talk() method to be used where a Talkative type is needed:

Talkative<Dog> td( aDog );
td.talk();
Talkative<CuckooClock> tcc(aCuckooClock);
tcc.talk();

Better yet, even classes which don't have a talk() method, but which provide equivalent functionality, can be made Talkative through user-defined specialisation:

template<>
void Talkative<BigBenClock>::talk() 
{
  t.playBongs(); 
}

Also through specialisation, missing functionality can be added, without affecting the original class code:

template<>
void Talkative<SilentClock>::talk() 
{ 
  cout << "tick tock" << endl; 
}

There is one way in which Java interfaces are somewhat more convenient than C++ template classes. You can use interfaces as formal parameters to a function, and any class which implements that interface can be passed as an argument. Because there is no implicit relationship between instantiated templates in C++, you either have to use a template function (like makeItTalk ), or derive the Talkative template from a non-template base class, and use that as the parameter type. However, flexibility can be achieved by relying on the C++ compiler's ability to deduce template arguments from an appropriate adapter function:

template <class T> 
void makeItTalk( Talkative<T> t )
{ 
  t.talk(); 
}
template <class T> 
Talkative<T> makeTalkative( T& obj )
{
   return Talkative<T>( obj ); 
}

Thus:

makeTalkative( aDog ).talk();
makeItTalk(makeTalkative(aBigBenClock));

Improved Code Management

You could argue that adding ' implements Talkative ' to the class definition is useful for documentation purposes. But you could also argue that it is intrusive on the design of domain classes, and modifying source code which you do not 'own' or which others share is sometimes undesirable or impossible for various reasons.

Creating a new Java subclass to add Talkative -ness to some domain object can be impossible if most classes are declared final (as was recommended by another article in JavaWorld). If an unrelated talk() function is defined by some class in the hierarchy, overriding that function to implement the Talkative interface could break existing code. These are issues that become more important as Java is used in larger, real-world projects. The C++ template approach has the advantage that polymorphism can be attached to a class object without requiring any change to that class's code.

As an additional advantage, template classes can define member variables and functionality not related to the class of the template argument. They are like interfaces that can be attached to objects for limited purposes and periods of time. And they add no overhead to the size of the object in memory, as abstract virtual base classes would.

There are proposals going through the Java Community Process to add support for "notions of genericity based on parametric polymorphism" (translation: some form of templates) to the Java language. You can read more about this proposal at java.sun.com/aboutJava/communityprocess/jsr/jsr_014_gener.html .

Here is the example program:

// talkativ.cpp
#include <iostream>
using std::cout;
using std::endl;
// some domain objects
class Dog 
{
public:
  void talk() const  
  { 
    cout << "woof woof" << endl; 
  }
};
class CuckooClock 
{
public:
  void talk() const 
  { 
    cout << "cuckoo cuckoo" << endl; 
  }
};
class BigBenClock 
{
public:
  void talk() const 
  { 
    cout<<"take a long tea-break"<<endl;
  }
  void playBongs() const 
  { 
    cout <<"bing bong bing bong" <<endl; 
  }
};
class SilentClock 
{
// doesn't talk
};
// generic template to provide 
// non-inheritance-based polymorphism
template <class T> 
class Talkative 
{
  T const & t;
public:
  Talkative(T const & obj) : t(obj) { }
  void talk() const { t.talk(); }
};
// specialization to adapt functionality
template <> class Talkative<BigBenClock> {
  BigBenClock const & t;
public:
  Talkative(BigBenClock& obj) : t(obj){}
  void talk() const { t.playBongs(); }
};
// specialization to add missing 
// functionality
template <> class 
Talkative<SilentClock> 
{
  SilentClock const & t;
public:
  Talkative(SilentClock& obj): t(obj) {}
  void talk() const 
{ 
      cout << "tick tock" << endl; 
  }
};
// adapter function to simplify syntax
// in usage
template <class T> 
Talkative<T> makeTalkative( T& obj ) 
{
  return Talkative<T>( obj );
}
// function to use an object which
// implements the Talkative interface,
// C++ style
template <class T> 
void makeItTalk(Talkative<T>t){t.talk();}
// test program
int main() 
{
  Dog aDog;
  CuckooClock aCuckooClock;
  BigBenClock aBigBenClock;
  SilentClock aSilentClock;
  Talkative<Dog> td(aDog);
  td.talk();
  Talkative<CuckooClock> 
                  tcc(aCuckooClock);
  tcc.talk();
  makeTalkative(aDog).talk();
  makeTalkative(aCuckooClock).talk();
  makeItTalk(makeTalkative(aBigBenClock));
  makeItTalk(makeTalkative(aSilentClock));
  return 0;
}

and its output:

woof woof
cuckoo cuckoo
woof woof
cuckoo cuckoo
bing bong bing bong
tick tock





Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.