避免虚函数

发布于 2024-10-14 18:10:24 字数 368 浏览 4 评论 0原文

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

void doYourJob();

让我们调用我想要最终将所有这些类放入同一个容器中的

,以便我可以循环遍历它们并让每个类执行“doYourJob()”显而易见的解决方案是使用该函数创建一个抽象类

 virtual void doYourJob();

,但我对此犹豫不决这样做。这是一个耗时的程序,而虚拟函数会大大削弱它的性能。此外,这个函数是类之间唯一的共同点,并且每个类的 doYourJob 的实现方式完全不同。

有没有办法避免使用带有虚函数的抽象类,或者我是否必须接受它?

So suppose I want to make a series of classes that each have a member-function with the same thing. Let's call the function

void doYourJob();

I want to eventually put all these classes into the same container so that I can loop through them and have each perform 'doYourJob()'

The obvious solution is to make an abstract class with the function

 virtual void doYourJob();

but I'm hesitant to do so. This is a time-expensive program and a virtual function would slime it up considerably. Also, this function is the only thing the classes have in common with each other and doYourJob is implimented completely differently for each class.

Is there a way to avoid using an abstract class with a virtual function or am I going to have to suck it up?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

蛮可爱 2024-10-21 18:10:25

虚拟功能的成本并不高。它们是间接调用,基本上就像函数指针一样。
性能成本是多少在 C++ 类中有一个虚拟方法?

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

Virtual functions don't cost much. They are an indirect call, basically like a function pointer.
What is the performance cost of having a virtual method in a C++ class?

If you're in a situation where every cycle per call counts, that is you're doing very little work in the function call and you're calling it from your inner loop in a performance critical application you probably need a different approach altogether.

┈┾☆殇 2024-10-21 18:10:25

我担心循环中的一系列 dynamic_cast 检查会使性能比虚拟函数更差。如果您要将它们全部放入一个容器中,那么它们需要具有某种共同的类型,因此您不妨将其设为一个纯虚拟基类,并在其中包含该方法。

在该上下文中,虚拟函数分派并没有太多内容:vtable 查找、提供的 this 指针的调整以及间接调用。

如果性能如此关键,您也许可以为每个子类型使用单独的容器并独立处理每个容器。如果顺序很重要,你会做很多后空翻,虚拟调度可能会更快。

I'm afraid that a series of dynamic_cast checks in a loop would slime up performance worse than a virtual function. If you're going to throw them all in one container, they need to have some type in common, so you may as well make it a pure-virtual base class with that method in it.

There's not all that much to the virtual function dispatch in that context: a vtable lookup, an adjustment of the supplied this pointer, and an indirect call.

If performance is that critical, you might be able to use a separate container for each subtype and process each container independently. If order matters, you'd be doing so many backflips that the virtual dispatch is probably faster.

爱她像谁 2024-10-21 18:10:25

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

我强烈建议首先尝试使用继承这一简单、直接的方法。如果这足够快,那就太好了!你完成了。如果不是,请尝试使用其他方案。永远不要因为成本而回避有用的语言功能,除非你有一些有力的证据表明成本太大。

If you're going to store all of these objects in the same container, then either you're going to have to write a heterogeneous container type (slow and expensive), you're going to have to store a container of void *s (yuck!), or the classes are going to have to be related to each other via inheritance. If you opt to go with either of the first two options, you'll have to have some logic in place to look at each element in the container, figure out what type it is, and then call the appropriate doYourJob() implementation, which essentially boils down to inheritance.

I strongly suggest trying out the simple, straightforward approach of using inheritance first. If this is fast enough, that's great! You're done. If it isn't, then try using some other scheme. Never avoid a useful language feature because of the cost unless you have some good hard proof to suggest that the cost is too great.

小糖芽 2024-10-21 18:10:24

如果您需要速度,请考虑在对象中嵌入“类型(识别)编号”,并使用 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)而言。即使如此,您也可以提供一个函数来一次获取和设置很多东西,从而最大限度地降低成本。人们过于担心虚拟调度——因此在找到尴尬的替代方案之前一定要先进行分析。它们的主要问题是它们执行了外联函数调用,尽管它们也使执行的代码离域化,从而改变了缓存利用模式(变得更好或更糟)。

If you need the speed, consider embedding a "type(-identifying) number" in the objects, and using a switch statement to select the type-specific code. This can avoid function call overhead completely - just doing a local jump. You won't get faster than that. A cost (in terms of maintainability, recompilation dependencies etc) is in forcing localisation (in the switch) of the type-specific functionality.


IMPLEMENTATION

#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';
  }
}

PERFORMANCE RESULTS

On my Linux system:

~/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

This suggests an inline type-number-switched approach is about (1.28 - 0.23) / (0.344 - 0.23) = 9.2 times as fast. Of course, that's specific to the exact system tested / compiler flags & version etc., but generally indicative.


COMMENTS RE VIRTUAL DISPATCH

It must be said though that virtual function call overheads are something that's rarely significant, and then only for oft-called trivial functions (like getters and setters). Even then, you might be able to provide a single function to get and set a whole lot of things at once, minimising the cost. People worry about virtual dispatch way too much - so do do the profiling before finding awkward alternatives. The main issue with them is that they perform an out-of-line function call, though they also delocalise the code executed which changes the cache utilisation patterns (for better or (more often) worse).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文