避免虚拟功能

hed*_*der 9 c++ code-organization

因此,假设我想创建一系列类,每个类都具有相同的成员函数.我们来调用这个函数吧

void doYourJob();
Run Code Online (Sandbox Code Playgroud)

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

显而易见的解决方案是使用该函数创建一个抽象类

 virtual void doYourJob();
Run Code Online (Sandbox Code Playgroud)

但我这样做犹豫不决.这是一个耗时的计划,虚拟功能可以大大提高它.此外,这个函数是类中唯一相互共同的函数,并且doYourJob对每个类的完全不同.

有没有办法避免使用具有虚函数的抽象类,或者我将不得不吮吸它?

Ton*_*roy 9

如果需要速度,请考虑在对象中嵌入"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';
  }
}
Run Code Online (Sandbox Code Playgroud)

表现结果

在我的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
Run Code Online (Sandbox Code Playgroud)

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


评论重新虚拟调度

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


Guy*_*ton 6

虚拟功能不会花费太多.它们是间接调用,基本上类似于函数指针. 在C++类中使用虚方法的性能成本是多少?

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