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

时间:2013-12-23 09:33:39

标签: c++ pointers inheritance

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

  1. 每个对象都有一个标志,每个函数都有一个switch语句。 标志的值将每个对象定向到其特定部分 各项功能。
  2. 在对象中有一个成员函数指针数组,它们是 在施工时分配。然后,我将该函数指针调用 获得正确的会员功能。
  3. 有一个包含多个派生类的虚基类。一 缺点是我的列表现在必须包含指针, 而不是对象本身。
  4. 我的理解是,选项3中列表中的指针查找将比选项2的成员函数查找花费更长时间,因为成员函数的保证接近。

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

3 个答案:

答案 0 :(得分:3)

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

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

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

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

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

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

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

    1. 共享了多少代码,有多少变化?
    2. 对象有多大,并且内联函数指针表会对缓存未命中统计数据产生重大影响吗?
    3. 并且,在你回答了这些之后,你应该只是简介。

答案 1 :(得分:2)

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

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

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

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

测量!

答案 2 :(得分:2)

实现更快的多态性的一种方法是通过CRTP idiom and static polymorphism

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!

静态多态性performs far better than virtual dispatch,因为编译器知道在编译时将调用哪个函数,并且它可以内联所有内容

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