简体   繁体   English

我可以在这里使用 Curiously Recurring Template Pattern (C++) 吗?

[英]Can I use the Curiously Recurring Template Pattern here (C++)?

I have a C++ application that can be simplified to something like this:我有一个 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)
};

My application is performance-critical.我的应用程序对性能至关重要。 There are typically thousands of widgets in the collection.集合中通常有数千个小部件。 The classes derived from AbstractWidget (of which there are dozens) typically leave many of the virtual functions not overridden.AbstractWidget派生的类(其中有几十个)通常不会覆盖许多虚函数。 The ones that are overridden typically have very fast implementations.被覆盖的那些通常具有非常快的实现。

Given this, I feel I can optimize my system with some clever meta-programming.鉴于此,我觉得我可以通过一些巧妙的元编程来优化我的系统。 The goal is to leverage function inlining and to avoid virtual function calls, while keeping the code managable.目标是利用 function 内联并避免虚拟 function 调用,同时保持代码可管理。 I've looked into the Curiously Recurring Template Pattern (see here for description).我研究了 Curiously Recurring Template Pattern(参见此处了解说明)。 This seems to almost do what I want, but not quite.这似乎几乎可以满足我的要求,但不完全是。

Is there any way to make the CRTP work for me here?有没有办法让 CRTP 在这里为我工作? Or, is there any other clever solution anyone can think of?或者,有没有人能想到任何其他聪明的解决方案?

Simulated dynamic binding (there are other uses of CRTP) is for when the base class thinks of itself as being polymorphic, but clients only actually care about one particular derived class. So for instance you might have classes representing an interface into some platform-specific functionality, and any given platform will only ever need one implementation.模拟动态绑定(还有 CRTP 的其他用途)适用于基数 class认为自己是多态的,但客户端实际上只关心一个特定的派生 class。因此,例如,您可能有表示某个平台特定接口的类功能,任何给定的平台都只需要一个实现。 The point of the pattern is to templatize the base class, so that even though there are multiple derived classes, the base class knows at compile time which one is in use.该模式的要点是将基类 class 模板化,这样即使有多个派生类,基类 class 在编译时也知道正在使用哪一个。

It doesn't help you when you genuinely need runtime polymorphism, such as for example when you have a container of AbstractWidget* , each element can be one of several derived classes, and you have to iterate over them.当您真正需要运行时多态性时,例如当您有一个AbstractWidget*容器时,它对您没有帮助,每个元素可以是几个派生类之一,您必须迭代它们。 In CRTP (or any template code), base<derived1> and base<derived2> are unrelated classes.在 CRTP(或任何模板代码)中, base<derived1>base<derived2>是不相关的类。 Hence so are derived1 and derived2 .因此derived1derived2 There's no dynamic polymorphism between them unless they have another common base class, but then you're back where you started with virtual calls.它们之间没有动态多态性,除非它们有另一个公共基数 class,但是你又回到了虚拟调用的起点。

You might get some speedup by replacing your vector with several vectors: one for each of the derived classes that you know about, and one generic one for when you add new derived classes later and don't update the container.您可以通过将向量替换为多个向量来获得一些加速:一个用于您知道的每个派生类,一个用于稍后添加新的派生类并且不更新容器时的通用向量。 Then addWidget does some (slow) typeid checking or a virtual call to the widget, to add the widget to the correct container, and maybe has some overloads for when the caller knows the runtime class. Be careful not to accidentally add a subclass of WidgetIKnowAbout to the WidgetIKnowAbout* vector.然后 addWidget 进行一些(缓慢的) typeid检查或对小部件的虚拟调用,以将小部件添加到正确的容器中,并且当调用者知道运行时 class 时可能有一些重载。注意不要意外添加WidgetIKnowAbout的子类到WidgetIKnowAbout*向量。 fooAll and barAll can loop over each container in turn making (fast) calls to non-virtual fooImpl and barImpl functions that will then be inlined. fooAllbarAll可以循环遍历每个容器,依次(快速)调用非虚拟的fooImplbarImpl函数,然后这些函数将被内联。 They then loop over the hopefully much smaller AbstractWidget* vector, calling the virtual foo or bar functions.然后他们遍历希望小得多的AbstractWidget*向量,调用虚拟foobar函数。

It's a bit messy and not pure-OO, but if almost all your widgets belong to classes that your container knows about, then you might see a performance increase.它有点混乱而且不是纯面向对象的,但是如果几乎所有小部件都属于您的容器知道的类,那么您可能会看到性能提升。

Note that if most widgets belong to classes that your container cannot possibly know about (because they're in different libraries, for example), then you can't possibly have inlining (unless your dynamic linker can inline. Mine can't).请注意,如果大多数小部件属于您的容器不可能知道的类(例如,因为它们在不同的库中),那么您就不可能进行内联(除非您的动态 linker 可以内联。我的不能)。 You could drop the virtual call overhead by messing about with member function pointers, but the gain would almost certainly be negligible or even negative.您可以通过摆弄成员 function 指针来降低虚拟调用开销,但收益几乎可以忽略不计甚至为负。 Most of the overhead of a virtual call is in the call itself, not the virtual lookup, and calls through function pointers will not be inlined.虚拟调用的大部分开销都在调用本身,而不是虚拟查找,并且不会内联通过 function 指针进行的调用。

Look at it another way: if the code is to be inlined, that means the actual machine code has to be different for the different types.换个角度看:如果要内联代码,则意味着不同类型的实际机器代码必须不同。 This means you need either multiple loops, or a loop with a switch in it, because the machine code clearly can't change in ROM on each pass through the loop, according to the type of some pointer pulled out of a collection.这意味着您需要多个循环,或者一个带有开关的循环,因为根据从集合中提取的某个指针的类型,机器代码显然不能在每次通过循环时在 ROM 中更改。

Well, I guess maybe the object could contain some asm code that the loop copies into RAM, marks executable, and jumps into.好吧,我想也许 object 可能包含一些 asm 代码,循环将其复制到 RAM 中,标记为可执行文件,然后跳入。 But that's not a C++ member function. And it can't be done portably.但这不是 C++ 成员 function。而且它不能移植。 And it probably wouldn't even be fast, what with the copying and the icache invalidation.它甚至可能不会很快,复制和 icache 失效是什么。 Which is why virtual calls exist...这就是存在虚拟呼叫的原因......

CRTP or compile-time polymorphism is for when you know all of your types at compile time. CRTP 或编译时多态性适用于在编译时知道所有类型的情况。 As long as you're using addWidget to collect a list of widgets at runtime and as long as fooAll and barAll then have to handle members of that homogenous list of widgets at runtime, you have to be able to handle different types at runtime.只要您使用addWidget在运行时收集小部件列表,并且只要fooAllbarAll必须在运行时处理同质小部件列表的成员,您就必须能够在运行时处理不同的类型。 So for the problem you've presented, I think you're stuck using runtime polymorphism.因此,对于您提出的问题,我认为您无法使用运行时多态性。

A standard answer, of course, is to verify that the performance of runtime polymorphism is a problem before you try to avoid it...一个标准的答案当然是在尝试避免它之前验证运行时多态性的性能是一个问题......

If you really need to avoid runtime polymorpism, then one of the following solutions may work.如果您确实需要避免运行时多态性,那么以下解决方案之一可能会奏效。

Option 1: Use a compile-time collection of widgets选项 1:使用小部件的编译时集合

If your WidgetCollection's members are known at compile time, then you can very easily use templates.如果您的 WidgetCollection 的成员在编译时是已知的,那么您可以很容易地使用模板。

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

Option 2: Replace runtime polymorphism with free functions选项 2:用自由函数替换运行时多态性

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

Ugly, and really not OO.丑陋的,真的不是OO。 Templates could help with this by reducing the need to list every special case;模板可以通过减少列出每个特殊情况的需要来帮助解决这个问题; try something like the following (completely untested), but you're back to no inlining in this case.尝试类似下面的操作(完全未经测试),但在这种情况下您又回到了无内联状态。

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

Option 3: Eliminate OO选项 3:消除 OO

OO is useful because it helps manage complexity and because it helps maintain stability in the face of change. OO 之所以有用,是因为它有助于管理复杂性,并且有助于在面对变化时保持稳定性。 For the circumstances you seem to be describing - thousands of widgets, whose behavior generally doesn't change, and whose member methods are very simple - you may not have much complexity or change to manage.对于您似乎正在描述的情况 - 成千上万的小部件,其行为通常不会改变,并且其成员方法非常简单 - 您可能没有太多复杂性或需要管理的变化。 If that's the case, then you may not need OO.如果是这样,那么您可能不需要 OO。

This solution is the same as runtime polymorphism, except that it requires that you maintain a static list of "virtual" methods and known subclasses (which is not OO) and it lets you replace virtual function calls with a jump table to inlined functions.此解决方案与运行时多态性相同,只是它要求您维护一个 static 的“虚拟”方法和已知子类(不是 OO)列表,并且它允许您使用跳转表替换虚拟 function 调用以内联函数。

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

The short answer is no.最简洁的答案是不。

The long answer (or still short campared to some other answers:-)长答案(或者与其他一些答案相比仍然很短:-)

You are dynamically trying to figure out what function to execute at runtime (that is what virtual functions are).您正在动态地尝试找出 function 在运行时执行什么(这就是虚函数)。 If you have a vector (whoses members can not be determined at compile time) then you can not work out how to inline the functions no matter what you try.如果你有一个向量(它的成员在编译时无法确定),那么无论你尝试什么,你都无法弄清楚如何内联函数。

The only caviat to that is that if the vectors always contain the same elements (ie you could work out a compile time what is going to be executed at runtime).唯一的问题是,如果向量始终包含相同的元素(即,您可以在编译时计算出将在运行时执行的内容)。 You could then re-work this but it would require somthing other than a vector to hold the elements (probably a structure with all the elements as members).然后你可以重新做这个,但它需要除向量之外的东西来保存元素(可能是一个以所有元素作为成员的结构)。

Also, do you really think that virtual dispatch is a bottleneck?另外,你真的认为虚拟调度是瓶颈吗?
Personally I highly doubt it.我个人非常怀疑。

The problem that you will have here is with WidgetCollection::widgets .您在这里遇到的问题是WidgetCollection::widgets A vector can only contain items of one type, and using the CRTP requires that each AbstractWidget have a different type, templatized by the desired derived type.向量只能包含一种类型的项目,使用 CRTP 要求每个AbstractWidget都有不同的类型,由所需的派生类型模板化。 That is, you're AbstractWidget would look something like this:也就是说,您的AbstractWidget看起来像这样:

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

Which means that each AbstractWidget with a different Derived type would constitute a different type AbstractWidget< Derived > .这意味着每个具有不同Derived类型的AbstractWidget将构成不同类型的AbstractWidget< Derived > Storing these all in a single vector won't work.将这些全部存储在一个向量中是行不通的。 So it looks like, in this case, virtual functions are the way to go.所以看起来,在这种情况下,虚函数是通往 go 的途径。

Not if you need a vector of them.如果您需要它们的向量,则不会。 The STL containers are completely homogeneous, which means that if you need to store a widgetA and a widgetB in the same container, they must be inherited from a common parent. STL 容器是完全同构的,这意味着如果您需要将一个 widgetA 和一个 widgetB 存储在同一个容器中,它们必须从一个共同的父级继承。 And, if widgetA::bar() does something different than widgetB::bar(), you have to make the functions virtual.而且,如果 widgetA::bar() 做的事情与 widgetB::bar() 不同,则必须使函数成为虚拟函数。

Do all of the widgets need to be in the same container?所有的小部件都需要在同一个容器中吗? You could do something like你可以做类似的事情

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

And then the functions wouldn't need to be virtual.然后函数就不需要是虚拟的。

Odds are that after you go through all that effort, you will see no performance difference.很可能在您 go 通过所有这些努力之后,您将看不到任何性能差异。

This is absolutely the wrong way to optimize.这绝对是错误的优化方式。 You wouldn't fix a logic bug by changing random lines of code would you?您不会通过更改随机代码行来修复逻辑错误,对吗? No, that's silly.不,那很愚蠢。 You don't "fix" code until you first find which lines are actually causing your problem.在您首先找到哪些行实际上导致了您的问题之前,您不会“修复”代码。 So why would you treat performance bugs differently?那么为什么要以不同的方式对待性能错误呢?

You need to profile your application and find where the real bottlenecks are.您需要分析您的应用程序并找到真正的瓶颈所在。 Then speed up that code and rerun the profiler.然后加速该代码并重新运行探查器。 Repeat until the performance bug (too slow execution) is gone.重复直到性能错误(执行速度太慢)消失。

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

相关问题 如何在C ++中强制使用奇怪的重复模板模式 - How to force use of curiously recurring template pattern in C++ C ++:奇怪的重复模板模式是什么?并且可以奇怪地重复模板模式取代虚拟功能? - C++: what is the Curiously-Recurring-Template-Pattern? and can Curiously-Recurring-Template-Pattern replace virtual functions? 奇怪的重复模板模式(CRTP),AutoLists和C ++ - Curiously Recurring Template Pattern (CRTP), AutoLists and C++ 奇怪的重复模板模式多态拷贝(C ++)中的继承 - Inheritance in curiously recurring template pattern polymorphic copy (C++) C ++ BigIntegers和奇怪的重复模板模式问题 - C++ BigIntegers and the Curiously Recurring Template Pattern Issue C ++反复出现的模板模式,语法错误 - C++ Curiously recurring template pattern, syntax error 具有可变参数模板的好奇重复模板模式(C ++) - Curiously recurring template pattern with variadic templates (C++) 如何使用“好奇重复性模板”模式 - How to use the “Curiously recurring template” pattern 好奇的重复模板模式(CRTP)是否是正确的解决方案? - Is the Curiously Recurring Template Pattern (CRTP) the right solution here? 如何将好奇重复模板模式用作桥模式? - How to use Curiously Recurring Template Pattern for Bridge Pattern?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM