C++ Standard Practice: Virtual Interface Classes VS. Templates

c++ standard practice: virtual interface classes vs. templates

You're basically right, dynamic polymorphism (inheritance, virtuals) is generally the right choice when the type should be allowed to change at runtime (for example in plugin architectures). Static polymorphism (templates) is a better choice if the type should only change at compile-time.

The only potential downsides to templates are that 1) they generally have to be defined in the headers (which means more code gets #included), and this often leads to slower compile-times.

But design-wise, I can't see any problems in using templates when possible.

Which complies more with standard c++
style?

Depends on what "standard C++ style" is. The C++ standard library uses a bit of everything. The STL uses templates for everything, the slightly older IOStreams library uses inheritance and virtual functions, and the library functions inherited from C uses neither, of course.

These days, templates are by far the most popular choice though, and I'd have to say that is the most "standard" approach.

C++ Interface vs Template

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.

Abstract classes vs. templates - good practices

You actually have more than one question, so, let's answer them one by one:

Which approach is better?

Neither is better in general. Each has is strengths and weaknesses. But you do come to an interesting point: on a more abstract level, those two are pretty much the same.

When having the choice should we implement things in a template way or abstract way in a library?

With templates you get:

  1. In general, faster execution. It can be much faster, 'cause a lot of inlining, and then optimization, can be done. OTOH, with an advanced de-virtualization compiler/linker and functions that can't be much inlined/optimized, you might get pretty much the same speed.

  2. Slower compile times. It can be much slower, especially if you go the "fancy template-meta-programming" way.

  3. Worse compiler errors. They can be much worse, especially if you go the "fancy template-meta-programming" way. When C++ gets support for concepts, one should be able to avoid this.

  4. If you design it carefully, improved type-safety. OTOH, if you're not careful, you'll end up in worse duck-typing than Smalltalk. Concepts would be a tool that could help here, too.

With virtual functions / interfaces, you get:

  1. De-coupled design, where, if you're careful, changes from one file won't require a re-compilation of others, and compile times can be much faster.
  2. Run-time polymorphism, meaning you can dynamically load code (it ain't as easy as it sounds, but, it's possible)
  3. Something that looks more familiar to someone who's experienced in OO.

Is there a reason for standard not to define some standard "Interfaces" like std::container or std::factory (just examples)?

One could find a lot of "low-level" reasons, I guess, but the fundamental reason is performance. That is, STL was designed to be "as fast as can be", and putting some (useful) interfaces "on top if it" now is pretty-much impossible.

When to use templates rather than derived classes

templates vs class hierarchy

The main thing is the cost of the virtual function calls. If the computational cost of the actual wok of the function is small then the cost of virtual dispatch can be significant. This is especially the case compared to an inlined function and if used inside tight loops.
Using runtime polymorphism rules out the possibility of functions being inlined.

C++ templates are a very powerful tool to have...

Along with the well known generic containers and algorithms like you find in the C++ standard library, C++ templates support a range of advanced techniques such as:

  • Policy based design
  • Template meta-programming

Essentially you are programming the compiler to build the classes you wanted. Used well this can give highly reusable optimal code. If misused it can give much more bloated object code than a more appropriate approach.

Interfaces vs Templates for dependency injection in C++

I think interface option is better, but one doesn't have to create common base class just for test. You can inherit your mock class from production class and override necessary methods. You'll have to make the methods virtual though, but that's how tools like mockpp work and they also allow automate this process a little bit.

Is it bad practice to use an abstract base class to enforce a common interface for a template parameter type?

It's bad practice because you're using the wrong tool for the job. You're writing code that is communicating the wrong things, and with no actual enforcement mechanism.

The point of dynamic polymorphism is that it's dynamic: defined at runtime such that you can pass objects to functions that don't fully know the type they're given at compile time. This allows code in one location to be written against the base class and not be exported into headers and the like. And the actual class being used at any point can also not be exported into headers and the like. Only the source of the class needs to know the actual type.

Compile-time polymorphism like templates is predicated on knowing everything at compile-time. All of the code, both source and destination, needs to know what the types are.

In essence, you like the way dynamic polymorphism spells out its requirements compared to compile-time polymorphism, but you try to side-step the performance costs of it by using compile-time polymorphism. This creates code confusion, as you're mixing up mechanisms.

If someone sees a base class with virtual functions, they're going to assume that your code will be passing pointers/references to the actual classes around. That's part of the expectation with using them in most cases (even virtual-based type erasure effectively do this). Seeing instead a bunch of template functions that take types by value will be confusing.

Additionally, you have no enforcement mechanism. A function which takes a FooBase& ensures that a user cannot call it with a type that is not an actual FooBase-derived type (well, you could make it implicitly convertible to one, but let's ignore perfidy here). Your template functions, eschewing concepts, have no similar enforcement. You can document that it must be a FooBase-derived type, but you don't statically enforce it.

At the very least, the template parameter should be declared as std::derived_from<FooBase> Foo (and no, static_assert is not a good idea).

But really, you should just use a proper concept. You want compile-time polymorphism, and whatever your personal feelings on concepts are, concepts are the language mechanism C++20 has for defining the prototype for compile-time polymorphism. Nobody will be confused as to the meaning and intent of your code if you use concepts.

Can virtual functions be replaced with auto parameters?

C++ has two different mechanisms in place to write a piece of code that behaves differently based on the types of the objects acted on:

  • virtual functions and inheritance, which work at runtime, and
  • template functions, which work at compile-time.

Your example with auto parameters (which apparently weren't actually adopted in C++14 except for lambda functions) works with templates. The code you've written is equivalent to

template <typename T>
void print(T& shape) {
cout << shape.name();
}

This code assumes that the type of T can be determined at compile-time, since the compiler needs to know the type of T in order to fill in the template. Once the compiler knows this it can say "Ah, I know what that type is! I'll generate code to directly call the name function in that type, and I know exactly what function that will call."

On the other hand, virtual functions and inheritance work at runtime. For example, suppose you want to write a function that reads some data from the network, then hands back either a Circle or a Rectangle. You might have some code like this:

Shape* myShape = decodeNetworkData();

Here, all the compiler knows is that myShape points to some sort of Shape, but it can't tell whether that's a circle or a square. Therefore, if you were to call

cout << myShape->name();

then the compiler would say "I know you're calling some version of name, but I don't know which one. But that's okay! I'll generate some code that looks at the dynamic type of myShape (the type of the thing it actually points at) and uses that to look up which function to call."

Notice that the code the compiler will generate in each case is different and the behavior will be different. In the first case, the compiler knows exactly which function to call. In the second, the compiler doesn't know what function to call, and has to generate some extra code to make things work. But, on the other hand, if you didn't have a Shape type with a virtual name function, you could make the "decode the bytes of the network" code snippet work with your first function, since the compiler would have to know, in advance, what type it was going to see come in over the network.

There was a proposal to mark this question as a duplicate of this older question on templates and inheritance, even though it's not superficially the same question. Once you know that the auto keyword in this context means "this is really a template function," you can look over that other proposed question to get some additional examples of the difference between static polymorphism (with templates) and runtime polymorphism (with virtual functions).

C++ templates for performance?

A common example is sorting.

In C, qsort takes a pointer to a comparison function. Generally speaking, there will be one copy of the qsort code, which is not inlined. It will make a call through the pointer to the comparison routine -- this of course is also not inlined.

In C++, std::sort is a template, and it can take a functor object as comparator. There is a different copy of std::sort for each different type used as a comparator. Assuming you use a functor class with overloaded operator(), then the call to the comparator can easily be inlined into this copy of std::sort.

So, templates give you more inlining because there are more copies of the sort code, each of which can inline a different comparator. Inlining is quite a good optimization, and sort routines do a lot of comparisons, so you can often measure std::sort running faster than an equivalent qsort. The cost of this is the chance of much larger code -- if your program uses a lot of different comparators then you get a lot of different copies of the sort routine, each with a different comparator baked into it.

In principle there's no reason why a C implementation can't inline qsort into the place it is called. Then if it was called with the name of the function, the optimizer could in theory observe that at the point it is used, the function pointer must still point to that same function. Then it can inline the call to the function, and the result would be similar to the result with std::sort. But in practice, compilers tend not to take the first step, inlining qsort. That's because (a) it's large, and (b) it's in a different translation unit, usually compiled into some library that your program is linked against, and (c) to do it this way, you'd have an inlined copy of qsort for every call to it, not just a copy for every different comparator. So it would be even more bloated than the C++, unless the implementation could also find a way to common up the code in cases where qsort is called in different places with the same comparator.

So, general-purpose functions like qsort in C tend to have some overheads on account of calls through function pointers, or other indirection[*]. Templates in C++ are a common way of keeping the source code generic, but ensuring that it compiles to a special-purpose function (or several such functions). The special-purpose code hopefully is faster.

It's worth noting that templates are not by any means just about performance. std::sort is itself more general-purpose than qsort in some ways. For example qsort only sorts arrays, whereas std::sort can sort anything that provides a random-access iterator. It can for example sort a deque, which under the covers is several disjoint arrays allocated separately. So the use of templates doesn't necessarily provide any performance benefit, it might be done for other reasons. It just happens that templates do affect performance.

[*] another example with sorting - qsort takes an integer parameter saying how big each element of the array is, and when it moves elements it therefore must call memcpy or similar with the value of this variable. std::sort knows at compile-time the exact type of the elements, and hence the exact size. It can inline a copy constructor call that in turn might translate to instructions to copy that number of bytes. As with the inlined comparator, it's often possible to copy exactly 4 (or 8, or 16, or whatever) bytes faster than you'd get by calling a routine that copies a variable number of bytes, passing it the value 4 (or 8, or 16, or whatever). As before, if you called qsort with a literal value for the size, and that call to qsort was inlined, then the compiler could perform the exact same optimization in C. But in practice you don't see that.



Related Topics



Leave a reply



Submit