Dyamic vs Static Polymorphism in C++ : which is pr

2020-05-19 08:25发布

问题:

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) ?

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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.