Since, there is no alternative proposed, which is the one I should follow from the ones I already have? The best option, in terms of OOP "rules".
C++ is a multi-paradigm programming language, so you don't need to use class hierarchies and virtual functions all the time (fortunately). Use OO if your problem is hierarchical, or if it can be described well in a hierarchical form.
To specialize algorithms in C++, you typically use function objects. A function object is a function pointer or class objects with an overloaded operator()
:
#include <iostream>
class Calc {
public:
template<class Op>
Calc(int arg, Op op)
: a(arg) {
op(a);
}
void print() {
std::cout << a << "\n";
}
private:
int a;
};
If the function object is only used in the constructor, you don't need to store it, hence you don't need to know its type in the class Calc
. We then can "templatize" only the constructor instead of the whole class.
This constructor however is unrestricted in its second parameter: It can take anything but will fail to compile if op(a)
is invalid. To restrict the types for Op
, concepts are currently being specified. Hopefully, they'll be published in this year (2014) as an ISO Technical Specification.
Until we get them, we can use ugly SFINAE techniques and static_assert
s to make the error messages better.
You can use inheritance here to express a concept, though. By using templates, you could still avoid virtual function calls. For example:
class MyInterface {
protected:
virtual void add(int& a) = 0;
};
class A final
: public MyInterface
{
public:
A(int& a, int c) {
a = a + c;
}
virtual void add(int& a) final override {
a += 100;
}
};
By making A
final, or just add
, the compiler can (could) infer that this is the final overrider of the virtual function, and avoid the dispatch.
class Calc {
public:
template<class Op>
Calc(int arg, Op op)
: a(arg) {
static_assert( std::is_base_of<MyInterface, Op>(),
"Op must implement MyInterface" );
op(a);
}
void print() {
std::cout << a << "\n";
}
private:
int a;
};
We can easily check if a class is derived from some other class, this can serve as a simplified version of a concept-check.
However, this constructor is still greedy: It produces an overload ranked as an Exact Match for all types in the second parameter. You'd have to use a SFINAE-technique to make it less greedy if that becomes a problem.