38

I have 2 solutions for the same problem - to make some kind of callbacks from one "controller" to the used object and I don't know what to chose.

Solution 1: Use interfaces

struct AInterface
{
    virtual void f() = 0;
};

struct A : public AInterface
{
    void f(){std::cout<<"A::f()"<<std::endl;}
};

struct UseAInterface
{
    UseAInterface(AInterface* a) : _a(a){}
    void f(){_a->f();}

    AInterface* _a;
};

Solution 2: Use templates

struct A
{
    void f(){std::cout<<"A::f()"<<std::endl;}
};

template<class T>
struct UseA
{
    UseA(T* a) : _a(a){}
    void f(){_a->f();}

    T* _a;
};

This is just a simple sample to illustrate my problem. In real world the interface will have several functions and one class may(and will!) implement multiple interfaces.

The code will not be used as a library for external projects and I don't have to hide the template implementation - I say this because first case will be better if I need to hide "controller" implementation.

Can you please tell me the advantages/disadvantages for each case and what is better to use?

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • 6
    You should have a virtual dtor on `AInterface`, otherwise objects that inherit from it might not have the destructors called. – Steve May 16 '13 at 11:53
  • 1
    @Steve: actually, not necessarily. You could also perfectly have a `protected` destructor. – Matthieu M. May 16 '13 at 12:18

7 Answers7

59

In my opinion performance should be ignored (not really, but micro optimizations should) until you have a reason for that. Without some hard requirements (this is in a tight loop that takes most of the CPU, the actual implementations of the interface member functions is very small...) it would be very hard if not impossible to notice the difference.

So I would focus on a higher design level. Does it make sense that all types used in UseA share a common base? Are they really related? Is there a clear is-a relationship between the types? Then the OO approach might work. Are they unrelated? That is, do they share some traits but there is no direct is-a relationship that you can model? Go for the template approach.

The main advantage of the template is that you can use types that don't conform to a particular and exact inheritance hierarchy. For example, you can store anything in a vector that is copy-constructible (move-constructible in C++11), but an int and a Car are not really related in any ways. This way, you reduce the coupling between the different types used with your UseA type.

One of the disadvantages of templates is that each template instantiation is a different type that is unrelated to the rest of the template instantiations generated out of the same base template. This means that you cannot store UseA<A> and UseA<B> inside the same container, there will be code-bloat (UseA<int>::foo and UseA<double>::foo both are generated in the binary), longer compile times (even without considering the extra functions, two translation units that use UseA<int>::foo will both generate the same function, and the linker will have to discard one of them).

Regarding the performance that other answers claim, they are somehow right, but most miss the important points. The main advantage of choosing templates over dynamic dispatch is not the extra overhead of the dynamic dispatch, but the fact that small functions can be inlined by the compiler (if the function definition itself is visible).

If the functions are not inlined, unless the function takes just very few cycles to execute, the overall cost of the function will trump the extra cost of dynamic dispatch (i.e. the extra indirection in the call and the possible offset of the this pointer in the case of multiple/virtual inheritance). If the functions do some actual work, and/or they cannot be inlined they will have the same performance.

Even in the few cases where the difference in performance of one approach from the other could be measurable (say that the functions only take two cycles, and that dispatch thus doubles the cost of each function) if this code is part of the 80% of the code that takes less than 20% of the cpu time, and say that this particular piece of code takes 1% of the cpu (which is a huge amount if you consider the premise that for performance to be noticeable the function itself must take just one or two cycles!) then you are talking about 30 seconds out of 1 hour program run. Checking the premise again, on a 2GHz cpu, 1% of the time means that the function would have to be called over 10 million times per second.

All of the above is hand waving, and it is falling on the opposite direction as the other answers (i.e. there are some imprecisions could make it seem as if the difference is smaller than it really is, but reality is closer to this than it is to the general answer dynamic dispatch will make your code slower.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    +1 design is dictated by the required abstractions, implementation and performance tuning come later. – TemplateRex May 16 '13 at 14:08
  • BTW, in my experience, the biggest overhead of runtime polymorphishm is the dynamic allocations that often come along with it. Consider e.g. a naive chess engine with a board struct holding 64 `IPiece*` that are created and deleted 1M+ times a second: it's not the pointer chasing that will kill performance here (but of course you can make a Flyweight and use board pointers to the 12 static piece types in it and eliminate the allocations). – TemplateRex May 16 '13 at 14:30
  • @rhalbersma: Correct, or alternatively you can use an allocator, or... do you really need to recreate the objects in the first place? But all these are unrelated to the template/OO approach and in that particular case a template would not help, since the assumption is that in the array you need to be able to hold different types of pieces – David Rodríguez - dribeas May 16 '13 at 14:32
  • Regarding the statement *"you can use types that don't conform to a particular and exact inheritance hierarchy"*, couldn't the same effect be achieved in most cases via adapters? If A is a template class that uses B passed as a template argument, say `A`, couldn't the methods of B used by A be encapsulated into an abstract class IB, which would then be derived from and implemented by an adapter of the concrete class we were passing to A via template? – Fabio A. Jun 01 '16 at 23:09
24

There are pros and cons to each. From the C++ Programming Language:

  1. Prefer a template over derived classes when run-time efficiency is at a premium.
  2. Prefer derived classes over a template if adding new variants without recompilation is important.
  3. Prefer a template over derived classes when no common base can be defined.
  4. Prefer a template over derived classes when built-in types and structures with compatibility constraints are important.

However, templates have their drawbacks

  1. Code that use OO interfaces can be hidden in .cpp/.CC files, whenever templates force to expose the whole code in the header file;
  2. Templates will cause code bloat;
  3. OO interfaces are explicit, whenever requirements to template parameters are implicit and exist only in developer's head;
  4. Heavy usage of templates hurts compilation speed.

Which to use depends on your situation and somewhat on you preferences. Templated code can produce some obtuse compilation errors which has lead to tools such as STL Error decrypt. Hopefully, concepts will be implemented soon.

Steve
  • 7,171
  • 2
  • 30
  • 52
  • +1 Nice answer! Obtuse is definitely a good adjective to use for Template compilation errors :) – Brady May 16 '13 at 12:22
  • Templates *can* cause code bloat, but not always. It depends how many different ways you'll instantiate that template, and how many of those instantiations the compiler can optimise away (by proving the object code works out identical to another one). A template approach *can* work out smaller than an inheritance-based one for the same reasons that lead to your rule 1. –  May 16 '13 at 13:16
  • Actually, a fair range of template capabilities could be implemented using similar methods to inheritance in principle - see generics in Java/Ada/etc and typeclasses in e.g. Haskell (which are different, but do a related job). I'd love to be able to easily switch between the two approaches without having to rewrite code, let alone switch languages - to be able to selectively trade code size for speed only where needed. –  May 16 '13 at 13:19
18

The template case will have slightly better performance, because no virtual call is involved. If the callback is used extremely frequently, favour the template solution. Note that "extremely frequently" doesn't really kick in until thousands per second are involved, probably even later.

On the other hand, the template has to be in a header file, meaning each change to it will force recompiling all sites which call it, unlike in the interface scenario, where the implementation could be in a .cpp and be the only file needing recompilation.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 2
    You could also mention a few bytes of overhead per instantiated virtual table (depending on compiler implementation) – nurettin May 16 '13 at 11:57
  • +1, but overuse of templates worries me (and will until we get [concepts](https://en.wikipedia.org/wiki/Concepts_%28C++%29)) because templates can be fragile and the error messages incredibly verbose and cryptic. There are also a few things you can do with inheritance that you can't do with templates (and plenty of visa versa, of course). –  May 16 '13 at 12:06
4

You could consider an interface like a contract. Any class deriving from it must implement the methods of the interface.

Templates on the other hand implicitly have some constraints. For example your T template parameter must have a method f. These implicit requirements should be documented carefully, error messages involving templates can be quite confusing.

Boost Concept can be used for concept checking, which makes the implcit template requirements easier to understand.

Dirk
  • 10,668
  • 2
  • 35
  • 49
1

The choice you describe is the choice between static polymorphism versus dynamic polymorphism. You'll find many discussions of this topic if you search for that.

It's hard to give a specific answer to such a general question. In general static polymorphism may give you better performance, but with the lack of Concepts in the C++11 standard also mean that you could get interesting compiler error messages when a class does not model the required concept.

dhavenith
  • 2,028
  • 13
  • 14
0

I would go with the template version. If you think about this in terms of performance then it makes sense.

Virtual Interface - Using virtual means that the memory for the method is dynamic and is decided at runtime. This has overhead in that it has to consult the vlookup table to locate that method in memory.

Templates - You get static mapping. This means when your method is called it does not have to consult the lookup table and is already aware of the location of the method in memory.

If you are interested in performance then templates are almost always the choice to go with.

Freddy
  • 2,249
  • 1
  • 22
  • 31
  • When will it make sense in terms of performance? Blank statements about performance are usually misleading if not wrong. The cost of dynamic dispatch is very small, the higher cost is that inhibits inlining, not the extra indirection. If the definitions of template argument type member functions is not visible at the place of call, and the functions are not trivial (they generate a few hundred processor instructions) the difference in cost will be small as in *won't be measurable* in most cases.... – David Rodríguez - dribeas May 16 '13 at 12:12
  • @DavidRodríguez-dribeas Hope I didn't oversell the whole performance thing. I know its small, but it still exist. If my post made it sound larger then it was.. that wasn't my intention. – Freddy May 16 '13 at 12:25
  • The problem is that there are many answers in the same line: dynamic dispatch will be slower. Most of them really miss the point, dynamic dispatch does not affect performance *in general*, only in few cases, and even there, it would have to be in a piece of code that takes a good amount of cpu to be noticeable at all. The main performance hit is not the dispatch but that it inhibits inlining, but there are other reasons why the compiler might not inline, and the extra indirection takes just one extra cpu instruction (an expensive one, but just a couple of cycles) – David Rodríguez - dribeas May 16 '13 at 12:41
0

How about option 3?

template<auto* operation, class Sig = void()>
struct can_do;

template<auto* operation, class R, class...Args>
struct can_do<operation, R(Args...)> {
  void* pstate = 0;
  R(*poperation)(void*, Args&&...) = 0;

  template<class T,
    std::enable_if_t<std::is_convertible_v<
      std::invoke_result_t<decltype(*operation), T&&, Args&&...>,
      R>,
    bool> = true,
    std::enable_if_t<!std::is_same_v<can_do, std::decay_t<T>>, bool> =true
  >
  can_do(T&& t):
    pstate((void*)std::addressof(t)),
    poperation(+[](void* pstate, Args&&...args)->R {
      return (*operation)( std::forward<T>(*static_cast<std::remove_reference_t<T>*>(pstate)), std::forward<Args>(args)... );
    })
  {}
  can_do(can_do const&)=default;
  can_do(can_do&&)=default;
  can_do& operator=(can_do const&)=default;
  can_do& operator=(can_do&&)=default;
  ~can_do()=default;

  auto operator->*( decltype(operation) ) const {
    return [this](auto&&...args)->R {
      return poperation( pstate, decltype(args)(args)... );
    };
  }
};

now you can do

auto invoke_f = [](auto&& elem)->void { elem.f(); };

struct UseA
{
  UseA(can_do<&invoke_f> a) : m_a(a){}
  void f(){(m_a->*&invoke_f)();}
  can_do<&invoke_f> m_a;
};

Test code:

struct A {
    void f() { std::cout << "hello world"; }
};
struct A2 {
    void f() { std::cout << "goodbye"; }
};

A a;
UseA b(a);
b.f();
A2 a2;
UseA b2(a2);
b2.f();

Live example.

Having a richer multi-operation interface on can_do is left as an exercise.

UseA is not a template. A and A2 have no common base interface class.

Yet it works.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524