c++ - 避免虚函数

标签 c++ code-organization

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

void doYourJob();

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

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

 virtual void doYourJob();

但我犹豫要不要这样做。这是一个耗时的程序,虚函数可以大大简化它。此外,此功能是类之间唯一的共同点,并且每个类的 doYourJob 实现方式完全不同。

有没有办法避免将抽象类与虚函数一起使用,或者我将不得不接受它?

最佳答案

如果您需要速度,请考虑在对象中嵌入“类型(标识)编号”,并使用 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 倍。当然,这特定于所测试的确切系统/编译器标志和版本等,但通常是指示性的。


关于虚拟调度的评论

必须要说的是,虚函数调用的开销很少很重要,而且仅适用于经常调用的琐碎函数(如 getter 和 setter)。即使那样,您也可以提供一个函数来同时获取和设置很多东西,从而最大限度地降低成本。人们太担心虚拟调度了——所以在找到尴尬的替代方案之前一定要进行分析。它们的主要问题是它们执行外联函数调用,尽管它们也使执行的代码离域化,这改变了缓存利用模式(更好或(更经常)更坏)。

关于c++ - 避免虚函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4801833/

相关文章:

module - Rust 中的跨模块函数调用

c++ - 将文件读入内存 C++:是否有用于 std::strings 的 getline()

c++ - 函数参数列表中的三个点是什么意思?

c# - 为什么调试器内存窗口在 VS 2015 中不可用?

c++ - C++ 中的 "query parameter"是什么?

matlab - 组织 MATLAB 类(class)的最佳方式?

javascript - AngularJS 最佳实践 - 风格指南

scripting - Octave 脚本部分

c++ - 如何访问其中有一对的 map

python - Python中的代码组织: Where is a good place to put obscure methods?