【问题标题】:CRTP to avoid virtual member function overheadCRTP 避免虚拟成员函数开销
【发布时间】:2011-10-09 15:52:42
【问题描述】:

CRTP to avoid dynamic polymorphism中,为了避免虚成员函数的开销,强加特定的接口,提出了如下方案:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo() {}; // required to compile. < Don't see why
};

struct your_type : base<your_type> {
  void foo() {}; // required to compile. < Don't see why
};

然而,派生类似乎不需要定义来编译,因为它继承了一个(代码编译良好,无需定义 my_type::foo)。实际上如果提供了一个函数,那么在使用派生类的时候就不会调用基函数了。

所以问题是,以下代码替换是否可以接受(和标准?):

template <class Derived>
struct base {
  void foo() {
    // Generate a meaningful error if called
    (void)sizeof( Derived::foo_IS_MISSING );
  };
};

struct my_type : base<my_type> {
  void foo() {}; // required to compile.
};

struct your_type : base<your_type> {
  void foo() {}; // required to compile.
};

int main() {
  my_type my_obj;
  my_obj.foo(); // will fail if foo missing in derived class
}

【问题讨论】:

  • 什么编译器和版本?使用 g++ 4.2/4.6 进行的简单测试无需在派生类型中添加声明即可编译。您确定没有其他问题被您误解为与此相关吗?您是否复制了失败的确切代码? (顺便说一句,它编译的事实并不意味着它是有意义的,如果没有在派生类中实现foo(),它将进入一个无限递归循环,如果尾递归优化是,它将以stackoverflow或无限循环结束执行。
  • 哦,我差点忘了……为什么你认为你需要优化虚拟调度?你真的测量过性能吗?很有可能您无缘无故地使系统复杂化。在考虑优化之前,您应该测量并确保您有性能问题,并且它与那段代码有关,并且删除虚拟调度会改善......然后,再次测量,因为您可能仍然是错了。
  • 由于您在base&lt;T&gt;::foo() 中(静态地)调用my_type::foo()your_type::foo(),因此您必须声明这些成员函数似乎是明智的,否则您将有直接的无限递归。
  • 这里的想法是帮助程序员定义一个“遵循”特定接口的派生类(通常是函子)。它不同于更常见的动态多态性用法。不过不确定这是个好主意。

标签: c++ templates virtual crtp


【解决方案1】:

据我了解,这种模式的全部意义在于,您可以简单地将参数传递为template &lt;typename T&gt; base&lt;T&gt; &amp;,并且您的接口由base&lt;T&gt; 中的(非虚拟)函数定义。如果您没有要定义的界面(正如您在问题的第二部分中建议的那样),那么首先就不需要任何这些。

请注意,您不是像纯虚函数那样“强加”一个接口,而是提供一个接口。由于一切都是在编译时解决的,因此“强加”并不是那么强烈的要求。

【讨论】:

  • 我想我误解了这种模式的主要用法,即动态多态性,而我正在寻找一种方法来强加一个没有虚函数的接口。我想通过明确提供派生类应该遵守的“接口”来帮助程序员。
  • @effer 你好像搞错了。 “动态多态性”根本不是 CRTP 的“主要用途”,因为两者是完全正交的。前者需要虚拟(至少不需要大量的跳跃来重新发明多态轮)。但是CRTP可以用来提供基础接口、成员等,是的。
【解决方案2】:

在您的替换代码中,您不能在 base&lt;T&gt; 上“多态地”调用 foo

【讨论】:

  • 我想我误解了这种模式的主要用法,即动态多态性,而我正在寻找一种方法来强加一个没有虚函数的接口。
【解决方案3】:

然而,派生类似乎不需要定义来编译,因为它继承了一个代码编译良好而无需定义一个 my_type::foo)。

C++ 是惰性的:如果你不实际使用它,它不会尝试生成 base::foo()。 但是如果你尝试使用它,它就会被创建,如果失败,编译错误就会出现。 但是在您的情况下, base::foo() 可以很好地实例化:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {};

void func() {
    my_type m;
    static_cast<base<my_type>& >(m).foo();
}

会编译得很好。当编译器出现 static_cast(this)->foo(),它将尝试找到一个在 my_type 中可访问的 foo()。还有一个:它叫做 base::foo(),它是从一个公共继承的类中公开的。所以 base::foo() 调用 base::foo(),你得到一个无限递归。

【讨论】:

  • 在修改后的示例中(使用 sizeof()),对 base::foo() 的调用应该会失败。我想我误解了这种模式的主要用途,即动态多态性,而我正在寻找一种方法来强加一个没有虚函数的接口。
【解决方案4】:

不,想象一下以下情况:

template <typename T>
void bar(base<T> obj) {
   obj.foo();
}

base<my_type> my_obj;
bar(my_obj);

将调用 Base 的 foo 而不是 my_type 的...

执行此操作,您将收到错误消息:

template <class Derived>
struct base {
  void foo() {
    sizeof(Derived::foo);
    static_cast<Derived *>(this)->foo();
  };
};

但我必须承认,我不确定这将如何在 GCC 以外的编译器中工作,仅使用 GCC 进行测试。

【讨论】:

  • 提议的修改在基类上仍然失败(这是我最初想要的),但似乎没有启用动态多态性。
  • 也许我误解了您所说的“动态”多态性是什么意思。动态多态是什么意思?
  • 似乎不是其他人的意思。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-19
  • 2011-07-25
  • 1970-01-01
  • 2011-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多