将C ++模板化对象存储为相同类型

时间:2011-01-08 15:55:14

标签: c++ performance templates

我有一个类是性能敏感代码路径的核心组件,所以我试图尽可能地优化它。这个班曾经是:

class Widget
{
    Widget(int n) : N(n) {}
    .... member functions that use the constant value N ....
    const int N;                // just initialized, will never change
}

构造函数的参数在编译时是已知的,因此我将此类更改为模板,以便可以将N编译为函数:

template<int N>
class Widget
{
   .... member functions that use N ....
}

我有一个方法的另一个类:

Widget & GetWidget(int index);

然而,在模板化Widget之后,每个小部件都有不同的类型,所以我不能再定义这样的函数了。我考虑了不同的继承选项,但我不确定模板的性能增益是否会超过继承函数调用的成本。

所以,我的问题是:

我很确定我想要两全其美(编译时/运行时),而且可能无法实现。但是,有没有办法在编译时获得知道N的性能,但是仍然可以将Widgets作为相同的类型返回?

谢谢!

5 个答案:

答案 0 :(得分:8)

这里的问题是,如果你将小部件存储为相同的类型,那么从该存储中检索小部件的代码(通过调用GetWidget)在编译时不知道N [* ]。调用构造函数的代码知道N,但使用该对象的代码必须处理多种可能性。

由于性能命中(如果有的话)很可能出现在使用小部件的代码中,而不是创建它们的代码,因此您无法避免在关键代码中执行某些取决于运行时信息。

可能是对类模板中实现的函数的虚拟调用,比在不知道值的情况下使用N的函数的非虚拟调用更快:

class Widget {
  public:
    virtual ~Widget() {}
    virtual void function() = 0;
};

template <int N>
class WidgetImpl : public Widget {
  public:
    virtual void function() { use N; }
};

当N已知时,优化器可能会发挥最佳作用,因为它可以最佳地展开循环,转换算术等等。但是通过虚拟呼叫,你可以看到一个很大的缺点,即没有任何一个呼叫可以被内联(我猜想虚拟呼叫比没有内联时的非虚拟呼叫更不可能被预测) )。内联未知N的收益可能超过了解N的收益,或者可能更少。尝试他们两个,看看。

对于更为牵强的努力,如果有相当少的常见案例,您甚至可以通过将关键小部件功能实现为以下内容来实现改进:

switch(n) {
    case 1: /* do something using 1 */; break;
    case 2: /* do the same thing using 2 */; break;
    default: /* do the same thing using n */; break;
};

为所有情况“做某事”,但默认情况下可能是对常量上模板化的函数的调用,则默认值是带有函数参数的代码,而不是模板参数。或者它可以全部调用相同的函数(带有函数参数),但是在参数是常量的情况下依赖编译器在优化之前内联调用,以获得与模板化时相同的结果。

不能大规模维护,并且通常不会像这样猜测优化器,但也许你知道常见的情况是什么,编译器没有。

[*]如果调用代码在编译时确实知道N的值,那么你可以用这样的函数模板替换GetWidget

template <int N>
Widget<N> &getWidget(int index) {
    return static_cast<Widget<N> &>(whatever you have already);
}

但我认为来电者不知道,因为如果确实如此,那么你可能不会问......

答案 1 :(得分:2)

您需要声明模板化类型继承的非模板化类型,然后将小部件存储为非模板化基类的指针。这是完成你所寻找的唯一(类型安全)方式。

但是,保留非模板化版本可能更清晰。您是否已经分析了代码,以确定运行时配置版本上的循环实际上是一个瓶颈?

答案 2 :(得分:2)

我猜以下不是一个选项?

template <int N>
Widget<N> & GetWidget();

无论如何,只要您一起管理多个窗口小部件类型,就不能再将它们模板化,因为您无法在一个容器中存储不同类型的对象。

迈克尔提出的非模板化基类是一种解决方案,但由于它会产生虚函数调用成本,我猜测使模板化的类没有任何好处。

答案 3 :(得分:2)

如果您的类型为finite and known,则可以使用boost::variant作为构造函数的参数。

  

变体类模板是安全的,   通用的,基于堆栈的区分   联合容器,提供简单   操纵对象的解决方案   来自一组异类的   统一的方式。而标准   容器如std :: vector可能是   被认为是“多价值,单一的   类型,“变体是”多种类型,单一   值“。

这里有一些伪代码

boost::variant< int, double, std::string > variant;
const variant foo( 1 );
const variant bar( 3.14 );
const variant baz( "hello world" );

const Widget foo_widget( foo );
const Widget bar_widget( bar );
const Widget baz_widget( baz );

或者,您可以使用boost::any more flexibility

答案 4 :(得分:1)

您可以编写模板化的GetWidget函数。这需要您在调用GetWidget时知道类型:

w = GetWidget<Box>(index);