Question

I think I understand the actual limitations of compile-time polymorphism and run-time polymorphism. But what are the conceptual differences between explicit interfaces (run-time polymorphism. ie virtual functions and pointers/references) and implicit interfaces (compile-time polymorphism. ie. templates).

My thoughts are that two objects that offer the same explicit interface must be the same type of object (or have a common ancestor), while two objects that offer the same implicit interface need not be the same type of object, and, excluding the implicit interface that they both offer, can have quite different functionality.

Any thoughts on this?

And if two objects offer the same implicit interface, what reasons (beside the technical benefit of not needing dynamic dispatch w/ a virtual function lookup table, etc) are there for not having these objects inherit from a base object that declares that interface, thus making it an explicit interface? Another way of saying it: can you give me a case where two objects that offer the same implicit interface (and therefore can be used as types to the sample template class) should not inherit from a base class that makes that interface explicit?

Some related posts:


Here's an example to make this question more concrete:

Implicit Interface:

class Class1
{
public:
  void interfaceFunc();
  void otherFunc1();
};

class Class2
{
public:
  void interfaceFunc();
  void otherFunc2();
};

template <typename T>
class UseClass
{
public:
  void run(T & obj)
  {
    obj.interfaceFunc();
  }
};

Explicit Interface:

class InterfaceClass
{
public:
  virtual void interfaceFunc() = 0;
};

class Class1 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc1();
};

class Class2 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc2();
};

class UseClass
{
public:
  void run(InterfaceClass & obj)
  {
    obj.interfaceFunc();
  }
};

An even more in-depth, concrete example:

Some C++ problems can be solved with either:

  1. a templated class whose template type provides an implicit interface
  2. a non-templated class that takes a base-class pointer which provides an explicit interface

Code that doesn't change:

class CoolClass
{
public:
  virtual void doSomethingCool() = 0;
  virtual void worthless() = 0;
};

class CoolA : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that an A would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

class CoolB : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that a B would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

Case 1. A non-templated class that takes a base-class pointer which provides an explicit interface:

class CoolClassUser
{
public:  
  void useCoolClass(CoolClass * coolClass)
  { coolClass.doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Case 2. A templated class whose template type provides an implicit interface:

template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser<CoolClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Case 3. A templated class whose template type provides an implicit interface (this time, not deriving from CoolClass:

class RandomClass
{
public:
  void doSomethingCool()
  { /* Do cool stuff that a RandomClass would do */ }

  // I don't have to implement worthless()! Na na na na na!
}


template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  RandomClass * c1 = new RandomClass;
  RandomClass * c2 = new RandomClass;

  CoolClassUser<RandomClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Case 1 requires that the object being passed in to useCoolClass() be a child of CoolClass (and implement worthless()). Cases 2 and 3, on the other hand, will take any class that has a doSomethingCool() function.

If users of the code were always fine subclassing CoolClass, then Case 1 makes intuitive sense, since the CoolClassUser would always be expecting an implementation of a CoolClass. But assume this code will be part of an API framework, so I cannot predict if users will want to subclass CoolClass or roll their own class that has a doSomethingCool() function.

No correct solution

Licensed under: CC-BY-SA with attribution
scroll top