我的一个项目中有很多自定义数据类型,它们都共享一个公共(public)的基类。

我的数据(来自数据库)的数据类型由基类的枚举来区分。我的体系结构允许使用派生类对特定数据类型进行专用处理,或者可以由基类处理它。

当构造一个特定的数据类型时,通常会直接调用构造函数:

Special_Type_X a = Special_Type_X("34.34:fdfh-78");
a.getFoo();

有一些模板魔术也可以像这样构造它:
Type_Helper<Base_Type::special_type_x>::Type a =  Base_Type::construct<Base_Type::special_type_x>("34.34:fdfh-78");
a.getFoo();

对于某些类型为enum的值,可能没有专门的含义,因此
Type_Helper<Base_Type::non_specialized_type_1>::Type == Base_Type

当我从数据库中获取数据时,在编译时不知道该数据类型,因此存在第三种构造数据类型的方法(来自QVariant):
Base_Type a = Base_Type::construct(Base_type::whatever,"12.23@34io{3,3}");

但是,当然,我希望调用正确的构造函数,因此该方法的实现过去看起来像:
switch(t) {
     case Base_Type::special_type_x:
        return Base_Type::construct<Base_Type::special_type_x>(var);

     case Base_Type::non_specialized_type_1:
        return Base_Type::construct<Base_Type::non_specialized_type_1>(var);

     case Base_Type::whatever:
        return Base_Type::construct<Base_Type::whatever>(var);

     //.....
}

这段代码是重复的,并且由于基类也可以处理新类型(添加到枚举中),因此我提出了以下解决方案:
// Helper Template Method
template <Base_Type::type_enum bt_itr>
Base_Type construct_switch(const Base_Type::type_enum& bt, const QVariant& v)
{
  if(bt_itr==bt)
    return Base_Type::construct<bt_itr>(v);
  return construct_switch<(Base_Type::type_enum)(bt_itr+1)>(bt,v);
}

// Specialization for the last available (dummy type): num_types
template <>
Base_Type construct_switch<Base_Type::num_types>(const Base_Type::type_enum& bt, const QVariant&)
{
  qWarning() << "Type" << bt << "could not be constructed";
  return Base_Type(); // Creates an invalid Custom Type
}

我原来的switch语句替换为:
return construct_switch<(Base_Type::type_enum)0>(t,var);

此解决方案按预期方式工作。

但是,编译后的代码不同。虽然原始switch语句的复杂度为O(1),但新方法导致O(n)复杂度。生成的代码递归调用我的助手方法,直到找到正确的条目。

为什么编译器不能正确地优化它?有没有更好的方法来解决这个问题?

类似问题:
Replacing switch statements when interfacing between templated and non-templated code

我应该提到我想避免C++11C++14并坚持使用C++03

最佳答案

这就是我所说的魔术开关问题-如何获取(一定范围的)运行时间值并将其转换为编译时间常数。
抽象地,您要生成此switch语句:

switch(n) {
  (case I from 0 to n-1: /* use I as a constant */)...
}
您可以使用参数包来生成类似于C++中的代码。
我将从c++14开始-替换样板:
template<unsigned...> struct indexes {typedef indexes type;};
template<unsigned max, unsigned... is> struct make_indexes: make_indexes<max-1, max-1, is...> {};
template<unsigned... is> struct make_indexes<0, is...>:indexes<is...> {};
template<unsigned max> using make_indexes_t = typename make_indexes<max>::type;
现在,我们可以轻松地创建一个从0到n-1的无符号整数的编译时序列。 make_indexes_t<50>扩展为indexes<0,1,2,3, ... ,48, 49>c++14版本以O(1)步骤执行此操作,因为大多数(全部?)编译器都使用内部函数实现std::make_index_sequence。上面的代码以线性(在编译时-在运行时不执行任何操作)递归深度和二次编译时内存来实现。这很糟糕,您可以在工作上做得更好(对数深度,线性内存),但是您是否有超过100种类型?如果没有,那就足够了。
接下来,我们构建一个回调数组。由于我讨厌C传统函数指针语法,因此我将抛出一些毫无意义的样板来隐藏它:
template<typename T> using type = T; // pointless boilerplate that hides C style function syntax

template<unsigned... Is>
Base_Type construct_runtime_helper( indexes<Is...>, Base_Type::type_enum e, QVariant const& v ) {
  // array of pointers to functions:  (note static, so created once)
  static type< Base_Type(const QVariant&) >* const constructor_array[] = {
    (&Base_Type::construct<Is>)...
  };
  // find the eth entry, and call it:
  return constructor_array[ unsigned(e) ](v);
}
Base_Type construct_runtime_helper( Base_Type::type_enum e, QVariant const& v ) {
  return construct_runtime_helper( make_indexes_t< Base_Type::num_types >(), e, v );
}
鲍勃是你的叔叔。 O(1)数组查找(具有O(n)设置,理论上可以在可执行程序启动之前完成)以进行调度。

1“鲍勃是你的叔叔”是英联邦的一句话,它的意思是“一切都已完成并可以正常工作”。

08-05 19:32