代码之家  ›  专栏  ›  技术社区  ›  David

我可以在这里使用奇怪的重复模板模式(C++)吗?

  •  7
  • David  · 技术社区  · 17 年前

    我有一个C++应用程序,可以简化为这样:

    class AbstractWidget {
     public:
      virtual ~AbstractWidget() {}
      virtual void foo() {}
      virtual void bar() {}
      // (other virtual methods)
    };
    
    class WidgetCollection {
     private:
      vector<AbstractWidget*> widgets;
    
     public:
      void addWidget(AbstractWidget* widget) {
        widgets.push_back(widget);
      }
    
      void fooAll() {
        for (unsigned int i = 0; i < widgets.size(); i++) {
          widgets[i]->foo();
        }
      }
    
      void barAll() {
        for (unsigned int i = 0; i < widgets.size(); i++) {
          widgets[i]->bar();
        }
      }
    
      // (other *All() methods)
    };
    

    AbstractWidget

    鉴于此,我觉得我可以通过一些巧妙的元编程来优化我的系统。目标是利用函数内联并避免虚拟函数调用,同时保持代码的可管理性。我已经研究了奇怪的重复模板模式(见 here 用于描述)。这似乎 几乎 做我想做的事,但不是完全。

    有什么办法让CRTP在这里为我工作吗?或者,还有其他任何人能想到的聪明解决方案吗?

    6 回复  |  直到 11 年前
        1
  •  5
  •   Josh Kelley    17 年前

    模拟动态绑定(CRTP还有其他用途)适用于 基类 客户 实际上只关心一个特定的派生类。因此,例如,您可能有一些类表示某个平台特定功能的接口,而任何给定的平台只需要一个实现。该模式的目的是对基类进行模板化,这样即使有多个派生类,基类也能在编译时知道哪个派生类在使用。

    AbstractWidget* base<derived1> 和 base<derived2> derived1 和 derived2 。除非它们有另一个公共基类,否则它们之间没有动态多态性,但这样你就回到了虚拟调用的起点。

    通过用几个向量替换你的向量,你可能会得到一些加速:一个用于你所知道的每个派生类,另一个用于以后添加新的派生类并且不更新容器时。然后addWidget会做一些(很慢) typeid 检查或虚拟调用小部件,将小部件添加到正确的容器中,并且当调用者知道运行时类时可能会有一些重载。注意不要意外添加的子类 WidgetIKnowAbout 到 WidgetIKnowAbout* 矢量。 fooAll 和 barAll fooImpl 和 barImpl 然后将内联的函数。然后,它们会绕过希望小得多的 抽象小部件* 向量,调用虚拟 foo 或 bar

    这有点混乱,也不是纯粹的面向对象,但如果你的小部件几乎都属于你的容器所知道的类,那么你可能会看到性能的提高。

    请注意,如果大多数小部件属于你的容器不可能知道的类(例如,因为它们在不同的库中),那么你不可能有内联(除非你的动态链接器可以内联。我的不能)。您可以通过摆弄成员函数指针来降低虚拟调用开销,但几乎可以肯定的是,收益可以忽略不计,甚至是负的。虚拟调用的大部分开销都在调用本身,而不是虚拟查找,通过函数指针的调用将不会内联。

    从另一个角度来看:如果代码要内联,这意味着不同类型的实际机器代码必须不同。这意味着你需要多个循环,或者一个带有开关的循环,因为根据从集合中提取的指针的类型,机器代码在每次通过循环时显然不能在ROM中更改。

        2
  •  7
  •   Steve Jessop    17 年前

    addWidget 在运行时收集小部件列表,只要 fooAll 和 barAll 然后必须在运行时处理同质小部件列表中的成员,您必须能够在运行时管理不同类型的小部件。因此,对于您提出的问题,我认为您只能使用运行时多态性。

    template<typename F>
    void WidgetCollection(F functor)
    {
      functor(widgetA);
      functor(widgetB);
      functor(widgetC);
    }
    
    // Make Foo a functor that's specialized as needed, then...
    
    void FooAll()
    {
      WidgetCollection(Foo);
    }
    

    class AbstractWidget {
     public:
      virtual AbstractWidget() {}
      // (other virtual methods)
    };
    
    class WidgetCollection {
     private:
      vector<AbstractWidget*> defaultFooableWidgets;
      vector<AbstractWidget*> customFooableWidgets1;
      vector<AbstractWidget*> customFooableWidgets2;      
    
     public:
      void addWidget(AbstractWidget* widget) {
        // decide which FooableWidgets list to push widget onto
      }
    
      void fooAll() {
        for (unsigned int i = 0; i < defaultFooableWidgets.size(); i++) {
          defaultFoo(defaultFooableWidgets[i]);
        }
        for (unsigned int i = 0; i < customFooableWidgets1.size(); i++) {
          customFoo1(customFooableWidgets1[i]);
        }
        for (unsigned int i = 0; i < customFooableWidgets2.size(); i++) {
          customFoo2(customFooableWidgets2[i]);
        }
      }
    };
    

    class AbstractWidget {
     public:
      virtual AbstractWidget() {}
    };
    
    class WidgetCollection {
     private:
      map<void(AbstractWidget*), vector<AbstractWidget*> > fooWidgets;
    
     public:
      template<typename T>
      void addWidget(T* widget) {
        fooWidgets[TemplateSpecializationFunctionGivingWhichFooToUse<widget>()].push_back(widget);
      }
    
      void fooAll() {
        for (map<void(AbstractWidget*), vector<AbstractWidget*> >::const_iterator i = fooWidgets.begin(); i != fooWidgets.end(); i++) {
          for (unsigned int j = 0; j < i->second.size(); j++) {
            (*i->first)(i->second[j]);
          }
        }
      }
    };
    

    选项3:消除OO

    OO很有用,因为它有助于管理复杂性,也有助于在面对变化时保持稳定性。对于您所描述的情况——数千个小部件,其行为通常不会改变,其成员方法非常简单——您可能没有太多的复杂性或变化需要管理。如果是这样的话,那么你可能不需要OO。

    class AbstractWidget {
     public:
      enum WidgetType { CONCRETE_1, CONCRETE_2 };
      WidgetType type;
    };
    
    class WidgetCollection {
     private:
      vector<AbstractWidget*> mWidgets;
    
     public:
      void addWidget(AbstractWidget* widget) {
        widgets.push_back(widget);
      }
    
      void fooAll() {
        for (unsigned int i = 0; i < widgets.size(); i++) {
          switch(widgets[i]->type) {
            // insert handling (such as calls to inline free functions) here
          }
        }
      }
    };
    
        3
  •  4
  •   Loki Astari    17 年前

    唯一的缺点是,如果向量总是包含相同的元素(即你可以在编译时计算出运行时要执行的内容)。然后,您可以重新工作,但需要除向量之外的其他东西来保存元素(可能是一个将所有元素作为成员的结构)。

    此外,您真的认为虚拟调度是一个瓶颈吗?
    就我个人而言,我对此深表怀疑。

        4
  •  3
  •   Naaff    17 年前

    你在这里遇到的问题是 WidgetCollection::widgets 一个向量只能包含一种类型的项,使用CRTP需要每个 AbstractWidget 看起来像这样:

    template< class Derived >
    class AbstractWidget {
        ...
        void foo() {
            static_cast< Derived* >( this )->foo_impl();
        }        
        ...
    }
    

    这意味着每个 抽象小部件 Derived AbstractWidget< Derived > 。将这些全部存储在一个向量中是行不通的。因此,在这种情况下,看起来虚拟函数是可行的。

        5
  •  3
  •   rlbond    17 年前

    所有的小部件都需要在同一个容器中吗?你可以做类似的事情

    vector<widgetA> widget_a_collection;
    vector<widgetB> widget_b_collection;
    

        6
  •  1
  •   T.E.D.    17 年前

    很有可能,在你付出所有努力之后,你不会看到任何表现上的差异。

    这绝对是 错误的 优化的方法。你不会通过随机更改代码行来修复逻辑错误,对吧?不,那太愚蠢了。在你第一次找到真正导致问题的行之前,你不会“修复”代码。那你为什么要治疗 演出

    你需要分析你的应用程序,找出真正的瓶颈在哪里。然后加速该代码并重新运行分析器。重复此操作,直到性能错误(执行速度太慢)消失。