繁体   English   中英

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

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

我试图分析实现多态的各种方法之间的权衡。 我需要一个对象列表,它们在成员函数中有一些相似之处和一些差异。 我看到的选项如下:

  1. 每个对象都有一个标志,每个函数都有一个switch语句。 标志的值将每个对象定向到每个函数的特定部分。
  2. 在对象中有一个成员函数指针数组,这些指针在构造时分配。 然后,我调用该函数指针以获取正确的成员函数。
  3. 有一个带有几个派生类的虚基类。 这样做的一个缺点是我的列表现在必须包含指针,而不是对象本身。

我的理解是,由于成员函数的保证接近,选项3中列表中的指针查找将比选项2的成员函数查找花费更长的时间。

这些选项有哪些优点/缺点? 我的首要任务是性能优于可读性。 有多态的其他方法吗?

  1. 每个对象都有一个标志,每个函数都有一个switch语句。 标志的值将每个对象定向到每个函数的特定部分

    好吧,如果基于标志的代码变化非常小,这可能是有意义的。 这最小化了必须适合高速缓存的(重复)代码量,并避免了任何函数调用间接。 在某些情况下,这些好处可能会超过switch语句的额外成本。

  2. 在对象中有一个成员函数指针数组,这些指针在构造时分配。 然后,我调用该函数指针以获取正确的成员函数

    您保存一个间接(到vtable),但也使您的对象更大,因此更少适合缓存。 不可能说哪个会占据主导地位,所以你只需要描述一下,但这不是一个明显的胜利

  3. 有一个带有几个派生类的虚基类。 这样做的一个缺点是我的列表现在必须包含指针,而不是对象本身

    如果您的代码路径不同,将它们完全分开是合理的,这是最干净的解决方案。 如果需要对其进行优化,可以使用专用的分配器来确保它们是顺序的(即使容器中没有顺序),也可以使用类似于Boost.Any的聪明包装器将对象直接移动到容器中。 你仍然会得到vtable间接,但我更喜欢这个#2,除非分析显示它真的是一个问题。

因此,在您决定之前,您应该回答几个问题:

  1. 共享多少代码,有多少变化?
  2. 对象有多大,并且内联函数指针表会对缓存未命中统计数据产生重大影响吗?

并且,在你回答了这些之后,你应该只是简介。

使用switch语句,如果要添加新类,则需要修改打开类的所有位置,这可能位于代码库中的不同位置。 您的代码库之外可能还有一些地方需要修改,但也许您知道在这种情况下不是这种情况。

使用每个成员中的成员函数指针数组,唯一的缺点是您为每个对象复制该内存。 如果您知道只有一个或两个“虚拟”功能,那么这是一个不错的选择。

至于虚函数,你是正确的,你必须堆分配它们(或手动管理内存),但它是最可扩展的选项。

如果您不是可扩展的,则(1)或(2)可能是您的最佳选择。 与往常一样,唯一的方法是衡量。 我知道许多编译器在某些情况下会通过跳转表实现switch语句,跳转表基本上与虚函数表相同。 对于少量的case语句,他们可能只使用二进制搜索分支。

测量!

实现更快的多态性的一种方法是通过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 );
}

这输出:

FOO!
酒吧!
QUUX!

静态多态性比虚拟分派执行得更好 ,因为编译器知道在编译时将调用哪个函数,并且它可以内联所有内容

即使它提供动态绑定,它也不能以通用的异构容器方式执行多态,因为基类的每个实例都是不同的类型。
但是,这可以通过boost::any来实现。

暂无
暂无

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

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