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

C++ c++;开关与成员函数指针与虚拟继承,c++,pointers,inheritance,C++,Pointers,Inheritance,我试图分析实现多态性的各种方法之间的权衡。我需要一个对象列表,在成员函数中有一些相似之处,也有一些不同之处。我看到的选项如下: 在每个对象中有一个标志,在每个函数中有一个switch语句。 标志的值将每个对象指向其特定的 每个功能 在对象中有一个成员函数指针数组,它们是 施工时分配的。然后,我调用该函数指针 获取正确的成员函数 具有具有多个派生类的虚拟基类。一个 这样做的缺点是我的列表现在必须包含指针, 而不是物体本身 我的理解是,选项3中的列表中的指针查找将比选项2的成员函数查找花费更长的时间

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

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


    这些选项有哪些优点/缺点?我优先考虑的是性能而不是可读性。
    多态性还有其他方法吗?

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

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

    至于虚拟函数,您是对的,您必须堆分配它们(或手动管理内存),但这是最可扩展的选项

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


    实现更快多态性的一种方法是:

    模板
    结构基
    {
    void f()
    {
    静态_cast(this)->f_impl();
    }
    };
    struct foo:公共基
    {
    无效f_impl()
    {
    标准::cout
    
  • 在每个对象中有一个标志,在每个函数中有一个switch语句。标志的值将每个对象指向每个函数的特定部分

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

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

    您只需保存一个间接寻址(到vtable),但也会使您的对象更大,因此缓存中的对象更少。无法确定哪一个将占主导地位,因此您只需配置文件,但这并不是一个明显的胜利

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

    如果您的代码路径差异很大,完全分离它们是合理的,那么这是最干净的解决方案。如果您需要优化它,您可以使用专门的分配器来确保它们是顺序的(即使在容器中不是顺序的),或者使用类似于Boost.Any的智能包装器将对象直接移动到容器中。您仍然可以获得vtable间接寻址,但我更喜欢使用此方法而不是#2,除非分析表明这确实是一个问题

  • 因此,在做出决定之前,您应该回答以下几个问题:

  • 有多少代码是共享的,有多少变化
  • 对象有多大,一个内联函数指针表是否会严重影响缓存未命中统计数据

  • 而且,在回答了这些问题之后,你还是应该简要介绍一下。

    1.糟糕,2.稍微不那么糟糕,3.你最好的选择,4.你需要多态性吗?通常你可以通过使用模板来解决这个问题……”我的优先考虑是性能而不是可读性-错得太厉害了。错得太厉害了。即使您需要性能,如果语言已经提供了虚拟函数(通过多态性--vtables any?),为什么还要手动重新实现虚拟函数呢?“我的首要任务是性能而不是可读性。”。在你有权说出这句话之前,你应该学会对代码进行基准测试/评测。上面有人评论。有时性能比可读性更重要,我认为认为认为他或她还没有评测是不礼貌的。这是一个有效的问题,不要因为它与你狭隘的编程世界观冲突而否决投票。你没有列出一种既可读又可能比所有3个模板都快的方法:模板。你可能还想看看静态多态性,即CRTP。
    至于虚拟函数,你是对的,你必须堆分配它们(或手动管理内存)
    不是真的。你只需要通过引用或指针传递对象(或者,很明显,直接用它的真正静态类型调用它)。对象是如何分配的完全不相关。我对堆栈上分配的对象做了大量多态性处理。最糟糕的是,Stroustrup在他的一个常见问题解答中也有同样的错误陈述。我想这就是人们不断重复这一点的原因。
    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 );
    }