11

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.

3
  • Maybe I'm missing something, but isn't the important difference already succinctly stated in your first paragraph, which is that explicit interfaces are run-time polymorphism, whereas implicit interfaces are compile-time polymorphism? Feb 10, 2012 at 18:22
  • 2
    There are some problems which can be solved by either having a Class or function that takes a pointer to an abstract Class (which provides an explicit interface), or by having a templated Class or function that uses an object that provides an implicit interface. Both solutions work. When would you want to use the first solution? The second? Feb 10, 2012 at 18:32
  • i think most of these considerations fall apart when you open concepts a little more. for example, where would you fit non-inheritance static polymorphism?
    – Javier
    Feb 10, 2012 at 19:04

1 Answer 1

10

You've already defined the important point- one is run-time and the other is compile-time. The real information you need is the ramifications of this choice.

Compiletime:

  • Pro: Compile-time interfaces are much more granular than run-time ones. By that, what I mean is that you can use only the requirements of a single function, or a set of functions, as you call them. You don't have to always do the whole interface. The requirements are only and exactly what you need.
  • Pro: Techniques like CRTP mean that you can use implicit interfaces to default implementations of things like operators. You could never do such a thing with run-time inheritance.
  • Pro: Implicit interfaces are much easier to compose and multiply "inherit" than run-time interfaces, and don't impose any kind of binary restrictions- for example, POD classes can use implicit interfaces. There's no need for virtual inheritance or other shenanigans with implicit interfaces- a big advantage.
  • Pro: The compiler can do way more optimizations for compile-time interfaces. In addition, the extra type safety makes for safer code.
  • Pro: It's impossible to do value typing for run-time interfaces, because you don't know the size or alignment of the final object. This means that any case which needs/benefits from value typing gains big benefits from templates.
  • Con: Templates are a bitch to compile and use, and they can be fiddly porting between compilers
  • Con: Templates cannot be loaded at run-time (obviously), so they have limits in expressing dynamic data structures, for example.

Runtime:

  • Pro: The final type doesn't have to be decided until run-time. This means that run-time inheritance can express some data structures much easier, if templates can do it at all. Also you can export run-time polymorphic types across C boundaries, for example, COM.
  • Pro: It's much easier to specify and implement run-time inheritance, and you won't really get any compiler-specific behaviour.
  • Con: Run-time inheritance can be slower than compile-time inheritane.
  • Con: Run-time inheritance loses type information.
  • Con: Run-time inheritance is way less flexible.
  • Con: Multiple inheritance is a bitch.

Given the relative list, if you do not need a specific advantage of run-time inheritance, do not use it. It is slower, less flexible, and less safe than templates.

Edit: It's worth noting that in C++ particularly there are uses for inheritance other than run-time polymorphism. For example, you can inherit typedefs, or use it for type tagging, or use the CRTP. Ultimately, though, these techniques (and others) really fall under "Compile-time", even though they are implemented using class X : public Y.

5
  • Regarding your first pro for compiletime, this is related to one of my main questions. Would you ever want to make it clear that you only want to work with an explicit interface. Ie. 'I don't care if you have all the functions I require, if you don't inherit from Class Z then I want nothing to do with you'. Also, run-time inheritance does not lose type info when using pointers/references, correct? Feb 10, 2012 at 18:45
  • @ChrisMorris: No. If it works then it works, which is all you should care about. Why make someone write the exact same code elsewhere?
    – jmoreno
    Feb 10, 2012 at 19:27
  • 1
    @ChrisMorris: No, I wouldn't. If I only need X, then it's one of the basic fundamental principles of encapsulation that I should only ever ask for and care about X. Also, it does lose type information. You can't, for example, stack allocate an object of such a type. You can't instantiate a template with it's true type. You cannot call templated member functions on them.
    – DeadMG
    Feb 10, 2012 at 21:41
  • What about a situation where you have a class Q that uses some class. Q takes a template parameter, so any class that provides the implicit interface will do, or so we think. It turns out that class Q also expects its internal class (call it H) to use Q's interface. For instance, when the H object is destructed, it should call some function of Q's. This cannot be specified in an implicit interface. Thus, templates fail. More clearly put, a tightly coupled set of classes that requires more than just implicit interfaces from each other seems to debar the use of templates. Feb 15, 2012 at 1:05
  • Con compiletime: Ugly to debug, necessity to put the definitions into the header
    – JFFIGK
    Mar 5, 2019 at 0:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.