Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/149.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在这种情况下,是否可以避免使用虚拟方法调用?_C++_Optimization - Fatal编程技术网

C++ 在这种情况下,是否可以避免使用虚拟方法调用?

C++ 在这种情况下,是否可以避免使用虚拟方法调用?,c++,optimization,C++,Optimization,我有一种数据类型,必须存储在一个连续数组中,为了更新该数据,该数组被迭代。棘手的部分是,我希望能够动态更改任何对象的更新方式 这就是我到目前为止的想法: struct Update { virtual void operator()(Data & data) {} }; struct Data { int a, b, c; Update * update; }; struct SpecialBehavior : public Update { void

我有一种数据类型,必须存储在一个连续数组中,为了更新该数据,该数组被迭代。棘手的部分是,我希望能够动态更改任何对象的更新方式

这就是我到目前为止的想法:

struct Update {
    virtual void operator()(Data & data) {}
};

struct Data {
    int a, b, c;
    Update * update;
};

struct SpecialBehavior : public Update {
    void operator()(Data & data) override { ... }
};
然后,我将为每个数据对象分配某种类型的更新。然后,在更新过程中,所有数据都会传递到其自己的更新函子:

for (Data & data : all)
   data->update(data);
据我所知,这就是所谓的战略模式


我的问题是:有没有更有效的方法?有什么方法可以实现同样的灵活性而不需要调用虚拟方法?

您可以使用函数指针

struct Data;

using Update = void (*)(Data &);

void DefaultUpdate(Data & data) {};

struct Data {
    int a, b, c;
    Update update = DefaultUpdate;
};

void SpecialBehavior(Data & data) { ... };
// ...
Data a;
a.update = &SpecialBehaviour;

这避免了虚函数的开销,但仍然有使用函数指针的开销(更小)。自C++11以来,还可以使用非捕获lambda(隐式转换为函数指针)


或者,您可以使用enum和switch语句

enum class UpdateType {
    Default,
    Special
};

struct Data {
    int a, b, c;
    UpdateType behavior;
};

void Update(Data & data) {
    switch(data.behavior) {
        case UpdateType::Default:
            DoThis(data);
            break;
        case UpdateType::Special:
            DoThat(data);
            break;
    }
}
如果您不需要开放集多态性(即,您预先知道将从
更新
)派生的所有类型),您可以使用变体,如
std::variant
boost::variant

struct Update0 { void operator()(Data & data) { /* ... */ } };
struct Update1 { void operator()(Data & data) { /* ... */ } };
struct Update2 { void operator()(Data & data) { /* ... */ } };

这将允许您:

  • 避免
    虚拟
    函数调用的成本

  • 以缓存友好的方式存储
    数据
    实例

  • 使用不同的接口或任意不同的状态更新类


另外,如果您希望只通过
操作符()(Data&)
接口允许开集多态性,则可以使用类似的方法,这基本上是对具有特定签名的函数对象的类型安全引用

struct Data {
    int a, b, c;
    function_view<void(Data&)> update_function;
};

虚拟函数调用的开销是多少?嗯,实施必须做两件事:

  • 从对象加载vtable指针
  • 从vtable加载函数指针

  • 这正是两种间接记忆。通过将函数指针直接放在对象中(避免从对象中查找vtable指针),可以避免这两种方法中的一种

    这样做的缺点是,它只适用于单个虚拟函数,如果添加更多,就会用函数指针填充对象,导致缓存压力更大,因此可能会降低性能。只要您只是替换一个虚拟函数,就可以再添加三个,这样您的对象就膨胀了24字节


    除非确保编译器能够在编译时派生实际类型的
    Update
    ,否则无法避免第二个内存间接寻址。而且,由于这似乎是在运行时使用虚拟函数来执行决策的全部目的,因此您运气不佳:任何试图“删除”该间接操作的尝试都会产生更差的性能


    (我用引号表示“remove”,因为您当然可以避免从内存中查找函数指针。代价是您正在执行类似于
    开关()
    else if()的操作。)
    从对象加载的某个类型标识值上的梯形图,这将比从对象加载函数指针的成本更高。中的第二个解决方案明确地做到了这一点,而
    std::variant
    方法通过将其隐藏在
    std::variant
    模板中来实现。间接寻址并不是真正的remo维德,它只是隐藏在更慢的操作中。)

    哼。有趣。投票表决。我想你可以用C语言,用一堆函数指针来完成。使用虚拟函数真的是这里的瓶颈吗?你能仔细检查一下你的指针和箭头吗?我认为目前还不太正确。您是否确定
    virtual
    调用的性能受到了影响?数据数组非常大,因此,即使每个对象进行很小的优化,也意味着总体上获得了很好的收益。但每个对象的成本非常小,这是对的。通常的建议是不要有一个异构集合,而是有多个异构集合。这只会删除一个间接寻址(从
    This
    指针中查找vtable指针),如果以这种方式实现了多个
    virtual
    函数,则会使对象膨胀。与仅声明函数
    virtual
    相比,后一点很容易降低性能。不幸的是,
    开关将比函数指针更慢。我想,只要只有一个函数可以切换,您的第一个解决方案实际上是最快的方法:-)“这避免了虚拟函数的成本,但仍然有使用函数指针的成本(更少)”-请引用您的来源,或者显示证明它的基准。@cmaster:开关将比函数指针更慢=>您测量过吗?当分支预测正确时,分支成本非常低。。。直到不可见为止。@cmaster由于内联了函数,可以从
    data
    ,…中提取公共部分、常量折叠数据,因此交换机具有更大的优化潜力,…您认为
    std::variant
    如何消除其不同类型之间的歧义?我敢打赌,它将包含一个整数,表示哪些类型当前有效。解释这样一个整数总是比
    vtable
    虚拟
    调用的间接方式慢。@cmaster-oth,在缓存中使用
    std::vector
    可能更好,因为所有数据都存储在连续内存中。在
    std::vector
    中使用抽象类通常需要稀疏的堆分配(尽管在大多数情况下,您可能不会注意到差异)@Justin关于连续内存的观点很好。再说一次,我认为这可能更有效
    struct Data {
        int a, b, c;
        std::variant<Update0, Update1, Update2> update;
    };
    
    for (Data & data : all)
    {
        std::visit(data.update, [&data](auto& x){ x(data); });
    }
    
    struct Data {
        int a, b, c;
        function_view<void(Data&)> update_function;
    };
    
    for (Data & data : all)
    {
        data.update_function(data);
    }