18

I understand that dynamic/static polymorphism depends on the application design and requirements. However, is it advisable to ALWAYS choose static polymorphism over dynamic if possible? In particular, I can see the following 2 design choice in my application, both of which seem to be advised against:

  1. Implement Static polymorphism using CRTP: No vtable lookup overhead while still providing an interface in form of template base class. But, uses a Lot of switch and static_cast to access the correct class/method, which is hazardous

  2. Dynamic Polymorphism: Implement interfaces (pure virtual classes), associating lookup cost for even trivial functions like accessors/mutators

My application is very time critical, so am in favor of static polymorphism. But need to know if using too many static_cast is an indication of poor design, and how to avoid that without incurring latency.

EDIT: Thanks for the insight. Taking a specific case, which of these is a better approach?

class IMessage_Type_1
{
  virtual long getQuantity() =0;
...
}
class Message_Type_1_Impl: public IMessage_Type_1
{
  long getQuantity() { return _qty;}
...
}

OR

template <class T>
class TMessage_Type_1
{
  long getQuantity() { return static_cast<T*>(this)->getQuantity(); }
...
}
class Message_Type_1_Impl: public TMessage_Type_1<Message_Type_1_Impl>
{
  long getQuantity() { return _qty; }
...
}

Note that there are several mutators/accessors in each class, and I do need to specify an interface in my application. In static polymorphism, I switch just once - to get the message type. However, in dynamic polymorphism, I am using virtual functions for EACH method call. Doesnt that make it a case to use static poly? I believe static_cast in CRTP is quite safe and no performance penalty (compile time bound) ?

vidit
  • 957
  • 1
  • 8
  • 23
  • 4
    A switch-case structure has the same complexity as using a vtable. – user877329 Mar 28 '12 at 12:08
  • I'm not sure why you think you end up with a lot of static_cast and switches when using static polymorphism - can you show some example code? – Michael Anderson Mar 28 '12 at 12:10
  • 1
    @MichaelAnderson: You'll get that if you try to use static polymorphism in a situation that requires dynamic polymorphism. In that case it's almost certainly better to use dynamic polymorphism, rather than trying to reinvent it. – Mike Seymour Mar 28 '12 at 12:12
  • @MikeSeymour Agreed. But even in those cases you can usually localize any such casts / switches to one or two functions using something akin to the visitor pattern. – Michael Anderson Mar 28 '12 at 12:16
  • One other way of using static polymorphism (non-extensible): Boost.Variant. Once again, a slightly different problem requires a slightly different tool. – Matthieu M. Mar 28 '12 at 12:32
  • One other thing: When speaking algorithms, templates may be instaciated with interfaces. So if I do not want a huge bunch of code to be generated for each type instance of a template i will always be able to say (for example) std::sort(foo,foo+N), if foo implements "LessThanComparable". It will produce less code, but is somewhat slower if the comparison operator is simple because it cannot use inlining. I got a factor of six when the predicate was one line. PII Deschutes so quite old computer. – user877329 Mar 30 '12 at 05:47

4 Answers4

16

Static and dynamic polymorphism are designed to solve different problems, so there are rarely cases where both would be appropriate. In such cases, dynamic polymorphism will result in a more flexible and easier to manage design. But most of the time, the choice will be obvious, for other reasons.

One rough categorisation of the two: virtual functions allow different implementations for a common interface; templates allow different interfaces for a common implementation.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 1
    `virtual functions allow different implementations for a common interface; templates allow different interfaces for a common implementation.` This doesn't really make sense. Static polymorphism is in fact allowing two different classes to create their own implementation, but the method has the same name and is called in both cases from the base class. – johnbakers May 27 '13 at 00:19
  • 1
    @Fellowshee In the most common use, a template class provides a different interface, but the same implementation, for each implementation type: e.g. `std::vector` provides a `push_back(double)` function, where as `std::vector` provides `push_back(int)`. Different interfaces; same implementation. Inheritance from a virtual `push_back(double)` would allow different implementations, but would not allow a different interface. – James Kanze May 28 '13 at 14:42
  • 1
    @JamesKanze Another very common use of templated design is, by using partial specializations, achieving the opposite, providing different optimized implementations according to the properties of the passed type. – ABu Aug 05 '20 at 09:37
9

A switch is nothing more than a sequence of jumps that -after optimized- becomes a jump to an address looked-up by a table. Exactly like a virtual function call is.

If you have to jump depending on a type, you must first select the type. If the selection cannot be done at compile time (essentially because it depends on the input) you must always perform two operation: select & jump. The syntactic tool you use to select doesn't change the performance, since optimize the same.

In fact you are reinventing the v-table.

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
  • I agree that switch case is basically a vtable. However, in dynamic polymorphism, we use virtuals for EVERY method, while in static we need to use to switch and cast once to get the derived type, after that there's no indirection. Added an example in the question. Is there a better way to implement this? – vidit Mar 29 '12 at 07:13
  • @vid: if the caller is allowed to know about the derived type, what is the purpose of polymophism at all? In this case the caller may interact with derived class directly, without any abstractions, interfaces, etc. Seems both polymorhism and inheritance are redundant here. If the caller isn't allowed to know about the derived type, then the type casting and method dispatching should be performed on each call inside the base class. vtable is optimal solution for such dispatching. CRPT is the right tool for optimizing calls from base to derived class (only) – user396672 Mar 29 '12 at 09:48
  • Ok, I appreciate your point. What I was trying to do was specify an interface using CRTP. So that, when other developers want to extend the library, they inherit the base class using CRTP again, and a compilation error is thrown if they fail to implement any method. Same logic as a pure virtual base class, but without the vtable overhead. Is there some idiom i can think of which implements this more appropriately? – vidit Mar 29 '12 at 10:47
  • 3
    @vid: Actually there are two interfaces we may discuss: (i) interface between library and external caller (client interface) (ii) interface between core library class and its descendant (extender interface). I suggest you to consider designing the two interfaces as two separate(!) tasks – user396672 Mar 30 '12 at 09:17
  • 2
    @vid: ... CRPT may really "specify" the only former (extender) interface (ii) The design of client interface (i) depends on possibility to expose derived class to client. If such exposition is impossible(undesired), then use virtual functions, there are no way to do it better. – user396672 Mar 30 '12 at 09:17
  • @vid: ...If the derived class may be exposed to client, then you don't need client interface at all (at least at runtime). The client may directly call all methods of your class without any dispatching or any other mediator. However, the derived class should implement some concept (i.e.set of methods with sertain signatures) . However, you may consider checking the concept at design time by provoking a compiler error in case of absence of some method required by your concept. You may incorporate concept checking code into CRPT base class, but this code will not actualy affect runtime at all. – user396672 Mar 30 '12 at 09:20
  • @vid: ... How to check a class for a given method, see, for instance, http://stackoverflow.com/questions/87372/is-there-a-technique-in-c-to-know-if-a-class-has-a-member-function-of-a-given (or ask your own question if it is not a dupe) – user396672 Mar 30 '12 at 09:24
6

You see the design issues associated with purely template based polymorphism. While a looking virtual base class gives you a pretty good idea what is expected from a derived class, this gets much harder in heavily templated designs. One can easily demonstrate that by introducing a syntax error while using one of the boost libraries.

On the other hand, you are fearful of performance issues when using virtual functions. Proofing that this will be a problem is much harder.

IMHO this is a non-question. Stick with virtual functions until indicated otherwise. Virtual function calls are a lot faster than most people think (Calling a function from a dynamically linked library also adds a layer of indirection. No one seems to think about that).

I would only consider a templated design if it makes the code easier to read (generic algorithms), you use one of the few cases known to be slow with virtual functions (numeric algorithms) or you already identified it as a performance bottleneck.

ebo
  • 8,985
  • 3
  • 31
  • 37
2

Static polimorphism may provide significant advantage if the called method may be inlined by compiler. For example, if the virtual method looks like this:

protected:
virtual bool is_my_class_fast_enough() override {return true;}

then static polimophism should be the preferred way (otherwise, the method should be honest and return false :).

"True" virtual call (in most cases) can't be inlined.

Other differences(such as additional indirection in the vtable call) are neglectable

[EDIT]

However, if you really need runtime polymorphism (if the caller shouldn't know the method's implementation and, therefore, the method can't be inlined on the caller's side) then do not reinvent vtable (as Emilio Garavaglia mentioned), just use it.

user396672
  • 3,106
  • 1
  • 21
  • 31