【问题标题】:C++: what is the Curiously-Recurring-Template-Pattern? and can Curiously-Recurring-Template-Pattern replace virtual functions?C++:什么是奇怪重复的模板模式? Curiously-Recurring-Template-Pattern 可以替代虚函数吗?
【发布时间】:2013-06-04 00:25:03
【问题描述】:

我没有对问题的准确描述,所以我只是想问一下这是否可能(如果是的话,其他一些信息会很好)。

一位程序员告诉我,您可以避免由虚函数/多态性引起的运行时开销。他说为了避免运行时开销,您可以在名为 Curiously_recurring_template_pattern 的模式中使用模板,它看起来像这样:

class Derived : public Base<Derived>
{
  // ... implementation here
};

这种奇怪重复的模板模式是如何工作的?

如何使用 Curiously-Recurring-Template-Pattern 来替代普通的虚函数/多态性?

我弄错了吗?

【问题讨论】:

  • 不能。两者完全不同,解决的问题也不同。但是,通常情况下,人们在应该使用 Y 时错误地使用了 X,然后建议是“使用 Y 而不是 X”。这并不意味着 X 和 Y 相关,只是人们不知道他们在做什么。
  • 哇,是CRTP模式。

标签: c++ templates virtual-functions crtp


【解决方案1】:

非常具体地说,可以使用 CRTP 代替具有虚函数的基类来实现 template method pattern,而无需虚函数调用开销。

使用虚函数,TMP 如下所示:

class ProvidesMethod {
protected:
  void Method() {
    // do something
    Part1();
    // do something else
    Part2();
    // do something final
  }

private:
  virtual void Part1() = 0;
  virtual void Part2() = 0;
};

class ExposesMethod : private ProvidesMethod {
public:
  using ProvidesMethod::Method;

private:
  void Part1() {
    // first part implementation
  }
  void Part2() {
    // second part implementation
  }
};

使用 CRTP,它看起来像这样:

template <typename Derived>
class ProvidesMethod {
protected:
  void Method() {
    // do something
    self().Part1();
    // do something else
    self().Part2();
    // do something final
  }

private:
  Derived& self() { return *static_cast<Derived*>(this); }
  const Derived& self() const { return *static_cast<const Derived*>(this); }
};

class ExposesMethod : private ProvidesMethod<ExposesMethod> {
public:
  using ProvidesMethod<ExposesMethod>::Method;

private:
  friend class ProvidesMethod<ExposesMethod>;
  void Part1() {
    // first part implementation
  }
  void Part2() {
    // second part implementation
  }
};

【讨论】:

  • 为什么需要“self”方法?为什么不直接调用 Part(); ?
  • 因为这将在Method 的上下文中使用this 的类型,即ProvidesMethod,它没有拥有 Part1 - 作为如果您只是尝试此更改,编译器将很快指出。或者,如果您的 CRTP 基础具有部件的默认实现,编译器将使用这些部件,派生类没有机会覆盖它们,因为它们不是虚拟调用。
  • 谢谢,现在看起来很明显 :)
【解决方案2】:

这被称为 CRTP(Curiously Recurring Template Pattern),所以你可以查一下。

虽然我真的不明白它如何取代经典的多态性......

另一方面,在某些情况下,可以通过模板替换复杂的类层次结构(有关更多信息,请参阅基于策略的设计),但这并不总是可行的......

【讨论】:

    【解决方案3】:

    正如 Julien 所说,这是 CRTP。你应该查一下。 但是 CRTP 不能代替虚函数。如果它适用于您的特定情况,那么您实际上并不需要虚拟功能。 看,模板提供编译时多态性。虚函数提供运行时多态性。如果您不知道在编译时将调用哪个覆盖,那么基于模板的解决方案将无法工作。如果您在运行时始终知道对象的真实类型,那么您就不需要虚函数。

    【讨论】:

      【解决方案4】:

      我不确定您将如何使用模板来提供类似虚函数的东西——这对我来说似乎很奇怪——当然没有一堆最终相当于实现你自己版本的虚函数而不是使用编译器提供的虚函数的诡计,我发现很难理解如何让调用函数的代码不知道对象是什么类型,并为该对象类型调用正确的函数。这就是虚函数的作用。

      此外,根据个人经验,虚函数的 ACTUAL 开销非常小,只有在非常极端的情况下才会产生影响(非虚函数内联的主要情况,而且开销如此微不足道调用函数的时间占总执行时间的很大一部分(这也意味着需要多次调用函数才能产生任何显着差异)。如果你必须“弄清楚要做什么”,就会有更多的开销(以其他方式使用 if 语句或类似的东西)[除了在“虚拟函数”上实现你自己的变体,但这只是重新发明轮子,除非你的新轮子比现有的轮子更好,否则这不是一个好主意]。

      【讨论】:

      • 我同意 CRTP 可能会在大部分时间里增加很少的现实生活性能增益。然而,内联代码的能力对于必须快速运行的短函数来说是一件严肃的事情。它避免了函数调用开销、堆栈使用,并且可以改进缓存。虚函数所需的间接查找可能很重要。
      • 我同意,但是如果你真的需要根据它是哪个类来执行不同的操作,则需要有一个 ifswitchfunction pointer 类型的解决方案。虚拟几乎总是其中最有效的。仅当 vtable 不在缓存中且 this 指针不在缓存中时,查找才有意义 - 如果您正在使用某些成员数据,至少 this 指针无论如何都需要加载到缓存中。
      • @MatsPetersson 不必有任何选择声明。 CRTP 像这样“模拟”虚拟调度:template &lt;typename Derived&gt; class Base { void foo() { ((Derived*)this)-&gt;pseudoVirtualBar(); } 这将根据模板参数(即派生类)调用不同的函数。
      • 正确,因此在执行“手动”多态时,它会将类型转换隐藏为正确的类型。如果您在编译时知道要选择什么,那就太好了。但是你也可以用更明显的演员来解决这个案例。无论哪种方式,除非我们有极端情况(例如,返回一个整数,或两个整数成员的总和,或类似的微不足道的东西),否则虚函数的开销相当低。
      • 我相信 CRTP 旨在“看起来像”一个多态结构,但所有绑定都是在编译时进行的;因此没有运行时开销。正如@Mats 所说,如果你必须做很多 ifs、switch 等来模拟运行时多态性,你最好使用真正的 virt 调用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-18
      • 2023-04-07
      • 1970-01-01
      • 2012-01-01
      • 2019-11-18
      相关资源
      最近更新 更多