假设“标准” C ++继承范例:
struct GeneralFunc
{
/*..members..*/
virtual double value(double a, double b) { return 0; }
};
struct Func_classA : GeneralFunc
{
/*..members..*/
double value(double a, double b) { return a * b; }
};
struct Func_classB : GeneralFunc
{
/*..members..*/
double value(double a, double b) { return a + b; }
};
void main(){
double a = 1.0, b = 1.0;
std::vector<GeneralFunc*> my_functions;
//fill my_functions from input
for (auto& f : my_functions)
{
double v = f->value(a, b);
}
}
我想要一个对迭代效率最高的实现,即最小化间接引用,最大化内联优化等。为了解决这个问题,我事先知道要实现的每个特定“类型”(我可以只定义所需的“ func”类型,而不必允许其他可能性)。
出现几个选项:
boost :: polycollection
#include <boost/poly_collection/base_collection.hpp>
//...rest the same
boost::base_collection<GeneralFunc> my_functions
//...rest the same
std :: variant
#include <variant>
//...rts
using funcs = std::variant<Func_classA, Func_classB /*..possibly more../*>
std::vector<funcs> my_functions
或CRTP(Curiously Recurring Template Pattern)
让我知道正确的命名方式,但是在这里,我基于“类型”(一种手动调度)来“转换”基类。
template<typename T>
struct GeneralFunc
{
/*..members..*/
int my_type;
double value(double a, double b) {
switch (my_type){
case TYPE_A:
return static_cast<Func_classA*>(this)->value(a,b);
/*..you get the idea..*/
我可以为了简化开发而牺牲边际效率,但是在这种情况下,关于“最佳实践”是否达成共识?
EDITS *修复了一些拼写错误;我当前的开发是 CRTP 最后选择的“开发中”。
解决方案:
经过测试,boost :: polycollection和std :: variant都是有效的方法。但是,事实证明这是最有效的(从内存来看,可能会略有下降)。
enum ftype { A = 0, B, C };
struct GeneralFunc
{
ftype my_type;
GeneralFunc(ftype t) : my_type(t) {}
inline double value(double a, double b) const; // delay definition until derived classes are defined
}
struct Func_classA : GeneralFunc
{
Func_classA() : GeneralFunc(ftype::A) {}
inline double value(double a, double b) const { return a * b; }
}
/* define B, C (& whatever) */
inline double GeneralFunc::value(double a, double b)
{
switch(my_type){
case (ftype::A):
return static_cast<Func_classA*>(this)->value(a,b);
/* same pattern for B, C, ect */
}
}
void main(){
std::vector<std::unique_ptr<GeneralFunc>> funcs;
funcs.push_back(std::make_unique<Func_classA>());
funcs.push_back(std::make_unique<Func_classB>());
funcs[0]->value(1.0,1.0); // calls Func_classA.value
funcs[1]->value(1.0,1.0); // calls Func_classB.value
}
答案 0 :(得分:1)
我很想只使用std::function
作为容器,而不是重写它。
using GeneralFunc = std::function<double(double, double);
struct Func_classA
{
/*..members..*/
double value(double a, double b) { return a * b; }
/*explicit*/ operator GeneralFunc () const { return [this](double a, double b){ value(a, b) }; }
};
struct Func_classB
{
/*..members..*/
double value(double a, double b) { return a + b; }
/*explicit*/ operator GeneralFunc () const { return [this](double a, double b){ value(a, b) }; }
};
void main(){
double a = 1.0, b = 1.0;
std::vector<GeneralFunc> my_functions;
//fill my_functions from input
for (auto& f : my_functions)
{
double v = f(a, b);
}
}
答案 1 :(得分:0)
我认为您没有包含一个选项(这是我用于性能关键代码的选项),即创建一个函数对象元组并在该元组上“迭代”。不幸的是,没有好的API可以遍历元组,因此必须实现自己的元组。参见下面的代码段
#include <tuple>
#include <functional>
template<int ... Id, typename Functions>
auto apply(std::integer_sequence<int, Id ...>, Functions& my_functions, double& v, double a, double b){
([](auto a, auto b){a=b;}(v, std::get<Id>(my_functions)( a, b )), ...);
}
int main(){
auto fA = [](double a, double b){return a*b;};
auto fB = [](double a, double b){return a+b;};
//create the tuple
auto my_functions=std::make_tuple(fA, fB);
double v=0;
double a = 1.;
double b = 1.;
//iterate over the tuple
apply(std::make_integer_sequence<int, 2>(), my_functions, v, a, b);
}
通过这种方式,您可以创建类型安全的零开销抽象,因为编译器知道有关所使用类型的所有信息(不需要任何类型擦除机制)。另外,不需要虚拟函数(与CRTP中相同),因此编译器可能会内联函数调用。上面的代码段使用C ++ 17通用lambda,也可以以C ++ 14或C ++ 11兼容的方式实现,但它会更冗长。我更喜欢CRTP,因为在我看来,CRTP更具可读性:没有对派生类的静态强制转换,也没有人为的继承层次结构。
编辑:从您的答案看来,您这里实际上不需要CRTP,使用CRTP解决方案编写的内容与此等效。
enum ftype { A = 0, B, C };
auto fA = [](double a, double b){return a*b;};
auto fB = [](double a, double b){return a+b;};
int main(){
std::vector<ftype> types(2);
types[0]=A;
types[1]=B;
auto value = [&types](double a, double b, ftype i){
switch(i){
case (ftype::A):
return fA(a,b);
break;
case (ftype::B):
return fB(a,b);
break;
}
};
double v=value(1., 1., A);
v=value(1., 1., B);
}
可能只是个口味问题,但是我认为上面的版本更具可读性(您实际上不需要通用的基类或将静态类型强制转换为派生类)。