简体   繁体   English

c ++开关与成员函数指针与虚拟继承

[英]c++ switch vs. member function pointer vs. virtual inheritance

I am trying to analyze the trade offs between various methods of achieving polymorphism. 我试图分析实现多态的各种方法之间的权衡。 I need a list of objects with some similarities and some differences in member functions. 我需要一个对象列表,它们在成员函数中有一些相似之处和一些差异。 The options I see are as follows: 我看到的选项如下:

  1. have a flag in each object, and a switch statement in each function. 每个对象都有一个标志,每个函数都有一个switch语句。 The value of the flag directs each object to its specific section of each function. 标志的值将每个对象定向到每个函数的特定部分。
  2. have an array of member function pointers in the object, which are assigned upon construction. 在对象中有一个成员函数指针数组,这些指针在构造时分配。 Then, I call that function pointer to get the correct member function. 然后,我调用该函数指针以获取正确的成员函数。
  3. have an virtual base class with several derived classes. 有一个带有几个派生类的虚基类。 One drawback to this is that my list will now have to contain pointers, and not the objects themselves. 这样做的一个缺点是我的列表现在必须包含指针,而不是对象本身。

My understanding is that the pointer lookups from the list in option 3 will take longer than the member function lookups of option 2 because of the guaranteed proximity of member functions. 我的理解是,由于成员函数的保证接近,选项3中列表中的指针查找将比选项2的成员函数查找花费更长的时间。

What are some of the benefits/drawbacks of these options? 这些选项有哪些优点/缺点? My priority is performance over readability. 我的首要任务是性能优于可读性。 Is there any other method for polymorphism? 有多态的其他方法吗?

  1. have a flag in each object, and a switch statement in each function. 每个对象都有一个标志,每个函数都有一个switch语句。 The value of the flag directs each object to its specific section of each function 标志的值将每个对象定向到每个函数的特定部分

    OK, so this could make sense if very little code varies based on the flag. 好吧,如果基于标志的代码变化非常小,这可能是有意义的。 This minimises the amount of (duplicated) code which has to fit in cache, and avoids any function call indirection. 这最小化了必须适合高速缓存的(重复)代码量,并避免了任何函数调用间接。 Under some circumstances these benefits could outweigh the extra cost of the switch statement. 在某些情况下,这些好处可能会超过switch语句的额外成本。

  2. have an array of member function pointers in the object, which are assigned upon construction. 在对象中有一个成员函数指针数组,这些指针在构造时分配。 Then, I call that function pointer to get the correct member function 然后,我调用该函数指针以获取正确的成员函数

    You save one indirection (to the vtable), but also make your objects bigger so fewer fit in cache. 您保存一个间接(到vtable),但也使您的对象更大,因此更少适合缓存。 It's impossible to say which will dominate, so you'll just have to profile, but it isn't an obvious win 不可能说哪个会占据主导地位,所以你只需要描述一下,但这不是一个明显的胜利

  3. have an virtual base class with several derived classes. 有一个带有几个派生类的虚基类。 One drawback to this is that my list will now have to contain pointers, and not the objects themselves 这样做的一个缺点是我的列表现在必须包含指针,而不是对象本身

    If the your code paths are different enough that separating them completely is reasonable, this is the cleanest solution. 如果您的代码路径不同,将它们完全分开是合理的,这是最干净的解决方案。 If you need to optimise it, you can either use a specialised allocator to ensure they're sequential (even if not sequential in your container), or move the objects directly into your container using a clever wrapper similar to Boost.Any. 如果需要对其进行优化,可以使用专用的分配器来确保它们是顺序的(即使容器中没有顺序),也可以使用类似于Boost.Any的聪明包装器将对象直接移动到容器中。 You'll still get the vtable indirection, but I'd prefer this to #2 unless profiling shows it's really a problem. 你仍然会得到vtable间接,但我更喜欢这个#2,除非分析显示它真的是一个问题。

So, there are several questions you should answer before you can decide: 因此,在您决定之前,您应该回答几个问题:

  1. how much code is shared, and how much varies? 共享多少代码,有多少变化?
  2. how big are the objects, and will a table of inline function pointers materially affect your cache miss stats? 对象有多大,并且内联函数指针表会对缓存未命中统计数据产生重大影响吗?

and, after you've answered those, you should just profile anyway. 并且,在你回答了这些之后,你应该只是简介。

With a switch statement, if you want to add a new class then you need to modify everywhere where the class is switched on, which may be in various places in your code base. 使用switch语句,如果要添加新类,则需要修改打开类的所有位置,这可能位于代码库中的不同位置。 There may also be places outside your code base that need to be modified, but perhaps you know this isn't the case in this scenario. 您的代码库之外可能还有一些地方需要修改,但也许您知道在这种情况下不是这种情况。

With an array of member function pointers within each member, the only downside is that you duplicate that memory for every object. 使用每个成员中的成员函数指针数组,唯一的缺点是您为每个对象复制该内存。 If you know there's only one or two "virtual" functions though then it's a good option. 如果您知道只有一个或两个“虚拟”功能,那么这是一个不错的选择。

As for virtual functions, you are right in that you have to heap allocate them (or manual manage the memory), but it is the most extensible option. 至于虚函数,你是正确的,你必须堆分配它们(或手动管理内存),但它是最可扩展的选项。

If you aren't after extensible, then (1) or (2) may be your best option. 如果您不是可扩展的,则(1)或(2)可能是您的最佳选择。 As always, the only way to tell is to measure. 与往常一样,唯一的方法是衡量。 I know that many compilers will implement a switch statement in some cases by a jump table, which essentially comes out the same as a virtual function table. 我知道许多编译器在某些情况下会通过跳转表实现switch语句,跳转表基本上与虚函数表相同。 For small numbers of case statement they may just use binary search branching. 对于少量的case语句,他们可能只使用二进制搜索分支。

Measure! 测量!

One way to achieve faster polymorphism is through the CRTP idiom and static polymorphism : 实现更快的多态性的一种方法是通过CRTP惯用法和静态多态性

template<typename T>
struct base
{
    void f()
    {
         static_cast<T*>( this )->f_impl();
    }
};

struct foo : public base<foo>
{
    void f_impl()
    {
       std::cout << "foo!" << std::endl;
    }
};

struct bar : public base<bar>
{
    void f_impl()
    {
       std::cout << "bar!" << std::endl;
    }
};

struct quux : public base<quux>
{
    void f_impl()
    {
       std::cout << "quux!" << std::endl;
    }
};


template<typename T>
void call_f( const base<T>& something )
{
    something.f();
}

int main()
{
    foo my_foo;
    bar my_bar;
    quux my_quux;

    call_f( my_foo );
    call_f( my_bar );
    call_f( my_quux );
}

This outputs: 这输出:

foo! FOO!
bar! 酒吧!
quux! QUUX!

Static-polymorphism performs far better than virtual dispatch , because the compiler knows which function will be called at compile-time, and it could inline everything . 静态多态性比虚拟分派执行得更好 ,因为编译器知道在编译时将调用哪个函数,并且它可以内联所有内容

Even if it provides dynamic binding, it cannot perform polymorphism in the common heterogeneous-container way, because every instance of the base class is a different type. 即使它提供动态绑定,它也不能以通用的异构容器方式执行多态,因为基类的每个实例都是不同的类型。
However, that could be achieved with something like boost::any . 但是,这可以通过boost::any来实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM