c++ standard practice: virtual interface classes vs. templates
I have to make a decision regarding generalization vs polymorphism.
Well the scenario is standard: I want to make my monolithic interdependent code to be more modular, clean and extensible. It is still in a stage where the change of design principle is doable, and, as I look at it, highly desirable.
Will I introduce purely virtual base classes (interfaces) or templates?
I am aware of the basics regarding the template option: less indirection, better performance, more compiling but no late binding, and so on.
The stl does not use much (or none?) inheritance, and boost doesn't either. But I think those are aimed to be really small basic tools that are used every 2 lines of code by the programmer.
I consider the inheritance and late binding approach to be more sensible for plug-in style of big pieces of code and functionality that should be interchangeable, updateable etc. after deployment or even during runtime.
Well my scenario lies somewhat inbetween.
I dont need to exchange pieces of code on the fly at runtime, compile time is fine. Usually it is also a very central and frequently used piece of functionality, it is not logically seperatable into big blocks.
This lets me tend somewhat to the template solution. To me it also looks somewhat cleaner.
Are there any big bad implications, are interfaces still THE way to go? When are they not? Which complies more with standard c++ style?
I know this is bordering on subjective, but I am really interested in some experiences. I don't own a copy of Scott Meyers effective C++ so I set my hopes on you guys :)
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.
Properties of classic object-oriented polymorphism:
- objects are bound at run-time; this is more flexible, but also consumes more resources (CPU) at run-time
- strong typing brings somewhat more type safety, but the need to
dynamic_cast
(and its potential to blow up into a customer's face) might easily compensate for that - probably more widely known and understood, but "classical" deep inheritance hierarchies seem horrendous to me
Properties of compile-time polymorphism by template:
- compile-time binding allows more aggressive optimizations, but prevents run-time flexibility
- duck-typing might seem more awkward, but failures are usually compile-time failures
- can sometimes be harder to read and understand; without concepts, compiler diagnostics might sometimes become enraging
Note that there is no need to decide for either one. You can freely mix and mingle both of them (and many other idioms and paradigms). Often, this leads to very impressive (and expressive) code. (See, for example, things like type erasure.) To get an idea what's possible through clever mixing of paradigms, you might want to browse through Alexandrescu's "Modern C++ Design".
It's something of a false opposition. Yes, the major use of inheritance and virtual functions is in iostreams
, which are very old, and are written in quite a different style to the rest of the std
libraries.
But many of the "coolest" modern C++ libraries such as boost do make use of runtime polymorphism, they just use templates to make it more convenient to use.
boost::any
and std::tr1::function
(formerly also from boost) are fine examples.
They are both single item containers to something whose concrete type is unknown at compile-time (this is especially obvious with any
, because it has its own kind of dynamic cast operator to get the value out).
After having shoveled a little more experience on my plate, there are some things in templates that I don't like: There are certain disadvantages that disqualify template meta programming from being a useable language:
- readability: too many brackets, too many non language enforced(therefore misused) conventions
- for someone undergoing the usual evolution in programming languages, templates are unreadable und incomprehensible (just look at boost bgl)
- Sometimes it feels like someone tried to write a c++ code generator in awk.
- compiler error messages are cl(utter)ed crap
- too many hacks necessary (most of them remedied in c++0x) to get some basic "language" like functionality.
- No Templates in implementation files resulting in header only libraries (which is a very two sided sword)
- usual IDE's code completion features are not of much help with templates.
- Doing big stuff in MPL seems "haggly", can't find another word for it. Every line of templated code produces constraints on that template type, which are enforced in a text-replacing kind of way. There are semantics inherent in inheritance hierarchies, there are none whatsoever in template structures. It's like everything is a void* and the compiler tries to tell you if there'll be a segfault.
All that being said, I use it quite successfully on basic utilities and libraries. Writing high-level functionality or hardware related stuff with it, doesn't seem to cut it for me. Meaning I template my building blocks but build the house the classic way.