避免虚拟功能

时间:2011-01-26 06:14:30

标签: c++ code-organization

所以假设我想创建一系列类,每个类都有一个具有相同功能的成员函数。我们来调用函数

void doYourJob();

我想最终将所有这些类放入同一个容器中,这样我就可以遍历它们并让每个类都执行'doYourJob()'

显而易见的解决方案是使用函数

创建一个抽象类
 virtual void doYourJob();
但是我对这样做犹豫不决。这是一个耗时的计划,虚拟功能可以大大提高它。此外,这个函数是类中唯一相同的函数,并且doYourJob对每个类的完全不同。

有没有办法避免使用带有虚函数的抽象类,或者我不得不把它搞砸了?

4 个答案:

答案 0 :(得分:8)

如果需要速度,请考虑在对象中嵌入“type(-identifying)数字”,并使用switch语句选择特定于类型的代码。这可以完全避免函数调用开销 - 只需执行本地跳转。你不会比这更快。成本(在可维护性,重新编译依赖性等方面)是强制类型特定功能的本地化(在交换机中)。


<强>实施

#include <iostream>
#include <vector>

// virtual dispatch model...

struct Base
{
    virtual int f() const { return 1; }
};

struct Derived : Base
{
    virtual int f() const { return 2; }
};

// alternative: member variable encodes runtime type...

struct Type
{
    Type(int type) : type_(type) { }
    int type_;
};

struct A : Type
{
    A() : Type(1) { }
    int f() const { return 1; }
};

struct B : Type
{
    B() : Type(2) { }
    int f() const { return 2; }
};

struct Timer
{
    Timer() { clock_gettime(CLOCK_MONOTONIC, &from); }
    struct timespec from;
    double elapsed() const
    {
        struct timespec to;
        clock_gettime(CLOCK_MONOTONIC, &to);
        return to.tv_sec - from.tv_sec + 1E-9 * (to.tv_nsec - from.tv_nsec);
    }
};

int main(int argc)
{
  for (int j = 0; j < 3; ++j)
  {
    typedef std::vector<Base*> V;
    V v;

    for (int i = 0; i < 1000; ++i)
        v.push_back(i % 2 ? new Base : (Base*)new Derived);

    int total = 0;

    Timer tv;

    for (int i = 0; i < 100000; ++i)
        for (V::const_iterator i = v.begin(); i != v.end(); ++i)
            total += (*i)->f();

    double tve = tv.elapsed();

    std::cout << "virtual dispatch: " << total << ' ' << tve << '\n';

    // ----------------------------

    typedef std::vector<Type*> W;
    W w;

    for (int i = 0; i < 1000; ++i)
        w.push_back(i % 2 ? (Type*)new A : (Type*)new B);

    total = 0;

    Timer tw;

    for (int i = 0; i < 100000; ++i)
        for (W::const_iterator i = w.begin(); i != w.end(); ++i)
        {
            if ((*i)->type_ == 1)
                total += ((A*)(*i))->f();
            else
                total += ((B*)(*i))->f();
        }

    double twe = tw.elapsed();

    std::cout << "switched: " << total << ' ' << twe << '\n';

    // ----------------------------

    total = 0;

    Timer tw2;

    for (int i = 0; i < 100000; ++i)
        for (W::const_iterator i = w.begin(); i != w.end(); ++i)
            total += (*i)->type_;

    double tw2e = tw2.elapsed();

    std::cout << "overheads: " << total << ' ' << tw2e << '\n';
  }
}

表现结果

在我的Linux系统上:

~/dev  g++ -O2 -o vdt vdt.cc -lrt
~/dev  ./vdt                     
virtual dispatch: 150000000 1.28025
switched: 150000000 0.344314
overhead: 150000000 0.229018
virtual dispatch: 150000000 1.285
switched: 150000000 0.345367
overhead: 150000000 0.231051
virtual dispatch: 150000000 1.28969
switched: 150000000 0.345876
overhead: 150000000 0.230726

这表明内联类型数转换方法的速度约为(1.28 - 0.23)/(0.344 - 0.23)= 9.2 。当然,这是针对确切的系统测试/编译器标志&amp;版本等,但通常是指示性的。


评论虚拟发送

必须要说的是,虚函数调用开销是很少有意义的东西,然后只适用于经常被称为普通函数(如getter和setter)。即便如此,您也许可以提供单一功能来同时获取和设置大量内容,从而最大限度地降低成本。人们担心虚拟调度方式太多 - 所以在找到尴尬的替代方案之前进行分析。它们的主要问题是它们执行一个外部函数调用,尽管它们也会对执行的代码进行非本地化,从而改变缓存利用率模式(更好或更常见)。

答案 1 :(得分:6)

虚拟功能不会花费太多。它们是间接调用,基本上类似于函数指针。 What is the performance cost of having a virtual method in a C++ class?

如果您处于每次调用的每个周期都很重要的情况,那么您在函数调用中所做的工作很少,并且您在性能关键应用程序中从内部循环调用它可能需要一个不同的完全接近。

答案 2 :(得分:4)

我担心循环中的一系列dynamic_cast检查会使性能比虚函数更糟糕。如果你要将它们全部放在一个容器中,它们需要有一些共同的类型,所以你也可以将它作为一个纯虚拟的基类,并使用该方法。

在该上下文中对虚函数调度没有那么多:vtable查找,对提供的this指针的调整以及间接调用。

如果性能非常重要,您可以为每个子类型使用单独的容器,并独立处理每个容器。如果订单很重要,那么你会做很多后空翻,以至于虚拟调度可能更快。

答案 3 :(得分:1)

如果您要将所有这些对象存储在同一个容器中,那么您将不得不编写异构容器类型(速度慢且价格昂贵),您将需要存储容器void * s(哎呀!),或者这些类必须通过继承相互关联。如果你选择使用前两个选项中的任何一个,你必须有一些逻辑来查看容器中的每个元素,找出它是什么类型,然后调用适当的doYourJob()实现,这基本上归结为继承。

我强烈建议首先尝试使用继承的简单,直接的方法。如果这个足够快,那太好了!你完成了。如果不是,那么尝试使用其他方案。除非你有一些很好的证据表明费用太高,否则永远不要避免使用有用的语言功能。