【问题标题】:Why do I want to use CRTP over simple template for static polymorphism?为什么我要在简单模板上使用 CRTP 来实现静态多态性?
【发布时间】:2020-07-23 21:06:46
【问题描述】:

我已经阅读了很多关于 Curiously Recurring Template Pattern 的帖子,但我仍然不明白为什么我不想使用它而不是仅仅使用模板编程。

以下是维基百科稍作修改的示例:

template <class T> 
struct Base
{
    void interface()
    {
        static_cast<T*>(this)->implementation();
    }
};

struct Derived : Base<Derived>
{
    void implementation();
};

但是,我可以用更直接的方式对模板做同样的事情:

template <class T> 
struct OuterClass
{
    void interface()
    {
        nested->implementation();
    }
private:
    T* nested;
};

struct NestedClass
{
    void implementation();
};

OuterClass<NestedClass> x;
x.interface();

CRTP 与我的实施相比有什么优势?

编辑:T* nested; 作为成员变量的行也可以只是T nested;,这样nested 是由外部类的构造函数创建的。

【问题讨论】:

  • 我能想到的最简单的答案是,在后一种情况下,使用该接口的开发人员需要知道类型是如何组合的——他们必须明确地说OuterClass&lt;NestedClass&gt;。在 CRTP 案例中,尤其是私有继承,这是他们不必知道的细节。旁注:在第二个示例中,OuterClass&lt;T&gt;::nested 未初始化。
  • 什么提供​​“T* 嵌套”?
  • @Klaus 它总是可以由默认构造函数提供
  • @cdhowie 对于你提出的第一点,我可以使用 typedef 来解决这个问题。

标签: c++ crtp


【解决方案1】:

我也可以这样做

不,您有一个额外的数据成员“T* nested”,它是在运行时设置的。因此,编译器并不完全了解它将看到的确切对象,并且可能优化得不太理想。此外,如果使用“this”,则可以通过指针进行间接寻址,因为在编译时就知道对任何数据成员的访问,即使调用的作者必须写static_cast&lt;T*&gt;(this)-&gt;something,但是完整的操作在编译时是完全已知的。

你所做的更像是一个虚函数调用,而不是编译时多态性。

另外你还得在某处设置嵌套指针,这意味着通过构造函数传递的东西,这在代码和速度上也是开销。

最后我看到,您需要分两步创建您的对象。首先是“嵌套”的,然后将其指针传递给外部类。这也可能导致性能下降,并可能产生诸如非连续内存、高速缓存行未命中等副作用。

【讨论】:

  • 等待...在第二种情况下(除非T::implementation() 是虚拟的)编译器确实知道编译时的完整操作。我在这里看到的唯一次优的事情是对*nested 的任何数据成员的额外间接级别。方法调用的分派方式仍与 CRTP 方法相同。
  • @cdhowie “在编译时知道完整的操作” 不!如果对方法的调用不需要数据成员访问,则可以将其设为静态。因此,它总是额外的间接性!
  • 可能对数据有额外的间接性,但这听起来像是编译器甚至无法解析最终的函数调用,直到运行时,这是错误的。编译器确切地知道将调用哪个函数(同样,假设该函数是非虚拟的)它只是不知道 this 在运行时之前将是什么。它可能仍会进行大量优化,包括内联函数调用。
  • @cdhowie:它知道如何访问代码,但不知道如何访问数据!并且调用从给定对象访问数据的方法,这在编译时是未知的,会导致优化效果不佳。再次重申:如果您在方法调用中不访问对象的数据,则将您的方法设为静态。在这种情况下,您根本不需要指针,但这与运行时或编译时多态性无关。
  • @Klaus 我不同意。而不是使用指针 T*,我可以使用从 OuterClass 的默认构造函数创建的成员变量作为“T 嵌套”来完成。在这种情况下,一切在编译时都是已知的。
【解决方案2】:

提供的解决方案几乎是基于虚函数的多态性的自制(假设示例不完整并且在其他地方有一些代码实际上设置了T*

运行时多态性(又称类型擦除)的主要缺点是性能。由于您需要取消引用指针,编译器通常无法在编译时知道调用了哪个函数,因此无法内联函数并就函数的副作用做出决定。最重要的是,通过指针调用通常需要一个额外的指针解引用步骤,这也会减慢速度。

【讨论】:

    猜你喜欢
    • 2021-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-25
    相关资源
    最近更新 更多