2013-12-23 60 views
5

我想分析实现多态的各种方法之间的权衡。我需要一个具有一些相似性和成员函数差异的对象列表。我看到的选项如下:c + +交换与成员函数指针与虚拟继承

  1. 在每个对象中都有一个标志,并且在每个函数中都有一个switch语句。 该标志的值将每个对象指向其每个函数的特定部分 。
  2. 在对象中有一个成员函数指针数组,这些成员函数指针在构造时分配为 。然后,我把那个函数指针调到 得到正确的成员函数。
  3. 有一个具有多个派生类的虚拟基类。其中一个 的缺点是,我的列表现在必须包含指针,而不是对象本身。

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

这些选项的一些优点/缺点是什么?性能优于可读性。 有没有其他的多态性方法?

+3

1.可怕的,2.稍微可怕的,3.你最好的选择,4.你需要多态吗?通常你可以通过使用模板来解决这个问题...... – Nim

+4

“我的优先考虑是性能优于可读性。” - 大错特错。如此可怕的错误。即使你需要性能,为什么你手动重新实现虚拟功能,如果语言已经提供了它们(通过多态 - vtables任何人?)? – 2013-12-23 09:37:49

+4

“我的优先考虑是性能优于可读性。”在你有权说出这个句子之前,你应该学习基准/简介代码。 –

回答

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); 
} 

此输出:

富!
酒吧!
quux!

静态多态performs far better than virtual dispatch,因为编译器知道哪个函数将在编译时被调用,它可以内嵌一切

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

2

随着switch声明,如果你想添加一个新的类,那么你需要无处不在类接通修改,这可能是在你的代码库的各个地方。您的代码库之外可能还有一些需要修改的地方,但也许您知道在这种情况下情况并非如此。

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

至于虚拟功能,你是对的,你必须堆分配它们(或手动管理内存),但它是最具扩展性的选项。

如果你不是可扩展的,那么(1)或(2)可能是你最好的选择。与往常一样,要说明的唯一方法就是衡量。我知道许多编译器会在某些情况下通过跳转表来实现一个switch语句,这个跳转表基本上与虚函数表一样。对于少数case声明,他们可能只使用二分查找分支。

措施!

+0

'至于虚拟功能,你是对的,你必须堆分配它们(或手动管理内存)'不正确。你只需要通过引用或指针来传递对象(或者,显然,直接用它的真正的静态类型来调用它)。对象如何分配完全不相关。我使用堆栈中分配的对象来做大量多态的东西。最糟糕的是Stroustrup在他的一个常见问题解答中有相同的错误陈述。我想这就是人们不断重复这一点的地方。 –

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

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

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

    您可以节省一个间接访问(对于vtable),但也会使对象变大,因此不适合缓存。不可能说哪个将主导,所以你只需要简介,但它不是一个明显的胜利

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

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

所以,有几个问题你应该回答,然后才能做出决定:

  1. 多少代码是共享的,以及有多少变化?
  2. 对象有多大,并且一个内联函数指针表会实质上影响你的缓存未命中状态?

而且,在您回答完这些问题后,您应该只是简介一下。