CRTP 和多级继承

Posted

技术标签:

【中文标题】CRTP 和多级继承【英文标题】:CRTP and multilevel inheritance 【发布时间】:2013-08-13 00:34:17 【问题描述】:

我的一个朋友问我“如何使用 CRTP 替换多级继承中的多态性”。更准确地说,在这样的情况下:

struct A 

  void bar() 
    // do something and then call foo (possibly) in the derived class:
    foo();
  

  // possibly non pure virtual
  virtual void foo() const = 0;


struct B : A 
  void foo() const override  /* do something */ 


struct C : B 
  // possibly absent to not override B::foo().
  void foo() const final  /* do something else */ 

我和我的朋友都知道 CRTP 不是多态性的直接替代品,但我们对可以同时使用这两种模式的情况感兴趣。 (为了这个问题,我们对每种模式的优缺点不感兴趣。)

    之前有人问过这个question,结果作者想实现命名参数成语,而他自己的answer更关注这个问题而不是CRTP .另一方面,投票最多的answer 似乎只是在基类中调用其同音词的派生类方法。

    我想出了一个答案(发布在下面),其中包含相当多的样板代码,我想知道是否有更简单的替代方案。

【问题讨论】:

看看这是不是你想要的: 【参考方案1】:

(1) 层次结构中最顶层的类如下所示:

template <typename T>
class A 

public:

  void bar() const 
    // do something and then call foo (possibly) in the derived class:
    foo();
  

  void foo() const 
    static_cast<const T*>(this)->foo();
  

protected:

  ~A() = default;

  // Constructors should be protected as well.

;

A&lt;T&gt;::foo() 的行为类似于纯虚方法,因为它没有“默认实现”并且调用被定向到派生类。但是,这并不妨碍A&lt;T&gt; 被实例化为非基类。为了获得这种行为,A&lt;T&gt;::~A() 被设为protected

备注:不幸的是,当使用= default; 时,GCC bug 会将特殊成员函数公开。在这种情况下,应该使用

protected:
    ~A() 

不过,对于对构造函数的调用与对析构函数的调用不匹配的情况(这可能通过operator new 发生),保护析构函数是不够的。因此,建议同时保护所有构造函数(包括复制构造函数和移动构造函数)。

A&lt;T&gt; 的实例化应该被允许并且A&lt;T&gt;::foo() 应该表现得像一个非纯虚方法时,那么A 应该类似于下面的模板类B

(2) 层次结构中间的类(或最上面的类,如上一段所述)如下所示:

template <typename T = void>
class B : public A<B<T>>  // no inherinace if this is the topmost class

public:

  // Constructors and destructor

  // boilerplate code :-(
  void foo() const 
    foo_impl(std::is_same<T, void>);
  

private:

  void foo_impl(std::true_type) const 
    std::cout << "B::foo()\n";
  

  // boilerplate code :-(
  void foo_impl(std::false_type) const 
    if (&B::foo == &T::foo)
      foo_impl(std::true_type);
    else
      static_cast<const T*>(this)->foo();
  

;

构造函数和析构函数是公共的,T 默认为 void。这允许B&lt;&gt; 类型的对象成为层次结构中派生最多的对象,并使其合法:

B<> b;
b.foo();

还要注意B&lt;T&gt;::foo() 在某种意义上表现为非纯虚方法,如果B&lt;T&gt; 是派生度最高的类(或者,更准确地说,如果Tvoid),那么b.foo();调用“默认实现 foo()”(输出B::foo())。如果T 不是void,则调用将定向到派生类。这是通过标签调度完成的。

需要测试&amp;B::foo == &amp;T::foo 以避免无限递归调用。实际上,如果派生类T 没有重新实现foo(),则调用static_cast&lt;const T*&gt;(this)-&gt;foo(); 将解析为B::foo(),它再次调用B::foo_impl(std::false_type)。此外,此测试可以在编译时解析,代码为if (true)if (false),优化器可以完全删除测试(例如带有-O3 的GCC)。

(3) 最后,层次结构的底部看起来像:

class C : public B<C> 

public:

  void foo() const 
    std::cout << "C::foo()\n";
  

;

或者,如果继承的实现 (B&lt;C&gt;::foo()) 足够,则可以完全删除 C::foo()

请注意,C::foo() 类似于 final 方法,因为调用它不会将调用重定向到派生类(如果有)。 (为了使它不是最终的,应该使用像 B 这样的模板类。)

(4)另见:

How to avoid errors while using CRTP?

【讨论】:

【参考方案2】:

注意:这并不是专门针对“最终覆盖”问题的解决方案,而是针对一般 CRTP 多级继承问题的解决方案(因为我在任何地方都没有找到关于如何做到这一点的答案,我认为我的发现会很有用)。

编辑:我已经发布了最终覆盖问题的解决方案here

我最近了解到 CRTP 及其作为运行时多态性的静态替代品的潜力。在搜索了一段时间后,看看 CRTP 是否可以用作多态性的类似“drop-in”替代品,这样您就可以使用多级继承等,我不得不说,我很惊讶如果没有可以无限扩展的样板,我在任何地方都找不到合适的通用解决方案。毕竟,考虑到 CRTP 的所有性能优势,为什么不尝试让 CRTP 成为多态性的替代品呢?随后进行了一些调查,这就是我想出的:

问题:

经典的 CRTP 模式在 CRTP 接口和实现类之间创建了可访问性的“循环”。 (CRTP 接口类可以通过自身对模板参数类型的静态转换来访问“基”实现类,并且实现类从 CRTP 接口类继承公共接口。)当你创建一个具体的实现时,你是关闭循环,使得从具体实现类继承变得非常困难,因此从它派生的任何东西也以多态方式表现。

Classic CRTP single-level inheritance

解决办法:

把模式分成三个概念:

“抽象接口类”,即 CRTP 接口。 “可继承的实现类”,可以无限期地被其他可继承的实现类继承。 “具体类”,将抽象接口与所需的可继承实现类结合起来,并关闭循环。

这里有一个图表来帮助说明:

Multiple-level inheritance with CRTP

诀窍是将具体实现类作为模板参数一直向下通过所有可继承的实现类传递到抽象接口类。

通过这种方法,您可以:

    无限期继承实现, 从任何级别调用 CRTP 多级继承链中实现最高的方法, 以与层次无关的方式设计每个实现, 忘记必须使用样板代码(好吧,反正不超过经典的单级 CRTP),

完美地反映了虚拟/运行时多态性。

示例代码:

#include <iostream>

template <class Top>
struct CrtpInterface

  void foo()
  
    std::cout << "Calling CrtpInterface::foo()\n";
    fooImpl();
  
  void foo2()
  
    std::cout << "Calling CrtpInterface::foo2()\n";
    fooImpl2();
  
  void foo3()
  
    std::cout << "Calling CrtpInterface::foo3()\n";
    fooImpl3();
  
  void foo4()
  
    std::cout << "Calling CrtpInterface::foo4()\n";
    fooImpl4();
  

// The "pure virtual functions"
protected:
  inline void fooImpl()
  
    top().fooImpl();
  
  inline void fooImpl2()
  
    top().fooImpl2();
  
  inline void fooImpl3()
  
    top().fooImpl3();
  
  inline void fooImpl4()
  
    top().fooImpl4();
  
  inline Top& top()
  
    return static_cast<Top&>(*this);
  
;

template<class Top>
class DefaultImpl : public CrtpInterface<Top>

  using impl = CrtpInterface<Top>;
  friend impl;

  void fooImpl()
  
    std::cout << "Default::fooImpl()\n";
  

  void fooImpl2()
  
    std::cout << "Default::fooImpl2()\n";
    std::cout << "Calling foo() from interface\n";
    impl::foo();
  

  void fooImpl3()
  
    std::cout << "Default::fooImpl3()\n";
    std::cout << "Calling highest level fooImpl2() from interface\n";
    impl::fooImpl2();
  

  void fooImpl4()
  
    std::cout << "Default::fooImpl4()\n";
    std::cout << "Calling highest level fooImpl3() from interface\n";
    impl::fooImpl3();
  
;

template<class Top>
class AImpl : public DefaultImpl<Top>

  using impl = CrtpInterface<Top>;
  friend impl;

  void fooImpl()
  
    std::cout << "A::fooImpl()\n";
  
;

struct A : AImpl<A>

;

template<class Top>
class BImpl : public AImpl<Top>

  using impl = CrtpInterface<Top>;
  friend impl;

  protected:
    BImpl()
      : i1
    
    

  private:
    int i;
    void fooImpl2()
    
      std::cout << "B::fooImpl2(): " << i << "\n";
    
;

struct B : BImpl<B>

;

template<class Top>
class CImpl : public BImpl<Top>

  using impl = CrtpInterface<Top>;
  friend impl;

  protected:
    CImpl(int x = 2)
      : ix
    
    

  private:
    int i;
    void fooImpl3()
    
      std::cout << "C::fooImpl3(): " << i << "\n";
    
;

struct C : CImpl<C>

  C(int i = 9)
    : CImpl(i)
  
  
;

template<class Top>
class DImpl : public CImpl<Top>

  using impl = CrtpInterface<Top>;
  friend impl;

  void fooImpl4()
  
    std::cout << "D::fooImpl4()\n";
  
;

struct D : DImpl<D>

;

int main()

  std::cout << "### A ###\n";
  A a;
  a.foo();
  a.foo2();
  a.foo3();
  a.foo4();

  std::cout << "### B ###\n";
  B b;
  b.foo();
  b.foo2();
  b.foo3();
  b.foo4();

  std::cout << "### C ###\n";
  C c;
  c.foo();
  c.foo2();
  c.foo3();
  c.foo4();

  std::cout << "### D ###\n";
  D d;
  d.foo();
  d.foo2();
  d.foo3();
  d.foo4();

哪些打印:

### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
D::fooImpl4()

使用这种方法和“变体样式”包装器(使用一些 sechsy 可变参数模板和宏构建,也许我稍后会发布),它就像一个指向虚拟抽象基类的指针,我能够有效地创建从同一接口继承的 CRTP 类的向量。

我测量了与同类虚拟类向量相比的性能,所有这些都基于等效的虚拟接口,我发现使用这种方法,根据场景,我可以实现性能提升到 8 倍!这是非常令人鼓舞的,因为生成功能多态 CRTP 类层次结构所需的开销相对较小!

【讨论】:

【参考方案3】:

在意识到我的original answer 并没有真正处理手头的最终覆盖问题后,我想我会添加它。我想以与我之前的答案类似的方式提出“最终覆盖”解决方案。

问题:

CRTP 接口类总是通过静态转换重定向到最高派生类。这与“final”函数的概念不一致:如果所需的“final”函数没有在最高派生类上实现,并且被更高的类“覆盖”(因为你不能给函数一个“final”属性,除非它是虚拟的,这是我们在 CRTP 中试图避免的),CRTP 接口将不会重定向到所需的“最终”函数,而是重定向到“覆盖”。

解决办法:

将界面拆分为三个概念:

没有任何重定向功能的抽象接口类,继承: 一个抽象重定向类,其重定向函数重定向到最高派生类,除非一个或多个重定向函数被以下内容覆盖: 一个具体的“重定向覆盖”类,它通过实现覆盖重定向功能。

在实例化具体实现类时,我们不是通过所有“可继承的实现类”将具体实现类作为模板参数传递到接口中,而是将接口将从其继承的重定向类作为模板参数传递。

当我们想要将函数设为“final”时,我们只需创建一个“重定向覆盖类”,它继承自抽象重定向类,并覆盖我们想要设为 final 的重定向函数。然后我们将这个新的“重定向覆盖类”作为参数传递给所有可继承的实现类。

采用这种方法:

    “final”函数被直接调用,而不是通过强制转换重定向(除非您需要在可继承的实现类而不是重定向覆盖类中实现“final”函数), “最终”函数不能被任何未来的用户代码覆盖, 每个“最终”函数只需要每个继承级别的额外 ImplFinal 类,没有额外的样板。

这听起来很复杂,所以这是我为了让事情更容易理解而制作的流程图:

DImpl 和 EImpl 具有 final 函数,当 DImpl 或 EImpl 继承自:

示例代码:

#include <iostream>
#include <type_traits> 

template <class Top>
struct Redirect

protected:
  // The "pure virtual functions"
  inline void fooImpl()
  
    top().fooImpl();
  
  inline void fooImpl2()
  
    top().fooImpl2();
  
  inline void fooImpl3()
  
    top().fooImpl3();
  
  inline void fooImpl4()
  
    top().fooImpl4();
  
  inline Top& top()
  
    // GCC doesn't allow static_cast<Top&>(*this) 
    // since Interface uses private inheritance
    static_assert(std::is_base_of<Redirect, Top>::value, "Invalid Top class specified.");
    return (Top&)(*this);
  
;

// Wraps R around the inner level of a template T, e.g:
// R := Redirect, T := X, then inject_type::type := Redirect<X>
// R := Redirect, T := A<B<C<X>>>, then inject_type::type := A<B<C<Redirect<X>>>>
template<template<class> class R, class T>
struct inject_type

  using type = R<T>;
;

template<template<class> class R, class InnerFirst, class... InnerRest, template<class...> class Outer>
struct inject_type<R, Outer<InnerFirst, InnerRest...>>

  using type = Outer<typename inject_type<R, InnerFirst>::type, InnerRest...>;
;

// We will be inheriting either Redirect<...> or something 
// which derives from it (and overrides the functions).
// Use private inheritance, so that all polymorphic calls can
// only go through this class (which makes it impossible to 
// subvert redirect overrides using future user code).
template <class V>
struct Interface : private inject_type<Redirect, V>::type

  using impl = Interface;

  void foo()
  
    std::cout << "Calling Interface::foo()\n";
    fooImpl();
  

  void foo2()
  
    std::cout << "Calling Interface::foo2()\n";
    fooImpl2();
  

  void foo3()
  
    std::cout << "Calling Interface::foo3()\n";
    fooImpl3();
  

  void foo4()
  
    std::cout << "Calling Interface::foo4()\n";
    fooImpl4();
  

private:
  using R = typename inject_type<::Redirect, V>::type;

protected:
  using R::fooImpl;
  using R::fooImpl2;
  using R::fooImpl3;
  using R::fooImpl4;
;

template<class V>
struct DefaultImpl : Interface<V>

  template<class>
  friend struct Redirect;

protected:
  // Picking up typename impl from Interface, where all polymorphic calls must pass through
  using impl = typename DefaultImpl::impl;

  void fooImpl()
  
    std::cout << "Default::fooImpl()\n";
  

  void fooImpl2()
  
    std::cout << "Default::fooImpl2()\n";
    std::cout << "Calling foo() from interface\n";
    impl::foo();
  

  void fooImpl3()
  
    std::cout << "Default::fooImpl3()\n";
    std::cout << "Calling highest level fooImpl2() from interface\n";
    impl::fooImpl2();
  

  void fooImpl4()
  
    std::cout << "Default::fooImpl4()\n";
    std::cout << "Calling highest level fooImpl3() from interface\n";
    impl::fooImpl3();
  
;

template<class V>
struct AImpl : public DefaultImpl<V>

  template<class>
  friend struct Redirect;

protected:
  void fooImpl()
  
    std::cout << "A::fooImpl()\n";
  
;

struct A : AImpl<A>

;

template<class V>
struct BImpl : public AImpl<V>

  template<class>
  friend struct Redirect;

protected:
  BImpl()
    : i1
  
  

private:
  int i;
  void fooImpl2()
  
    std::cout << "B::fooImpl2(): " << i << "\n";
  
;

struct B : BImpl<B>

;

template<class V>
struct CImpl : public BImpl<V>

  template<class>
  friend struct Redirect;

  protected:
    CImpl(int x = 2)
      : ix
    
    

  private:
    int i;
    void fooImpl3()
    
      std::cout << "C::fooImpl3(): " << i << "\n";
    
;

struct C : CImpl<C>

  C(int i = 9)
    : CImpl(i)
  
  
;

// Make D::fooImpl4 final
template<class V>
struct DImplFinal : public V

protected:
  void fooImpl4()
  
    std::cout << "DImplFinal::fooImpl4()\n";
  
;


// Wrapping V with DImplFinal overrides the redirecting functions
template<class V>
struct DImpl : CImpl<DImplFinal<V>>

;

struct D : DImpl<D>

;

template<class V>
struct EImpl : DImpl<V>

  template<class>
  friend struct Redirect;

protected:
  void fooImpl()
  
    std::cout << "E::fooImpl()\n";
  

  void fooImpl3()
  
    std::cout << "E::fooImpl3()\n";
  

  // This will never be called, because fooImpl4 is final in DImpl
  void fooImpl4()
  
    std::cout << "E::fooImpl4(): this should never be printed\n";
  
;

struct E : EImpl<E>

;

// Make F::fooImpl3 final
template<class V, class Top>
struct FImplFinal : public V

protected:
  // This is implemented in FImpl, so redirect
  void fooImpl3()
  
    top().fooImpl3();
  

  // This will never be called, because fooImpl4 is final in DImpl
  void fooImpl4()
  
    std::cout << "FImplFinal::fooImpl4() this should never be printed\n";
  

  inline Top& top()
  
    // GCC won't do a static_cast directly :( 
    static_assert(std::is_base_of<FImplFinal, Top>::value, "Invalid Top class specified");
    return (Top&)(*this);  
  
;

// Wrapping V with FImplFinal overrides the redirecting functions, but only if they haven't been overridden already
template<class V>
struct FImpl : EImpl<FImplFinal<V, FImpl<V>>>

  template<class>
  friend struct Redirect;
  template<class, class>
  friend struct FImplFinal;

protected:
  FImpl() 
    : i99 
  
  

  // Picking up typename impl from DefaultImpl
  using impl = typename FImpl::impl;

private:
  int i;

  void fooImpl2()
  
    std::cout << "F::fooImpl2()\n";
    // This will only call DFinal::fooImpl4();
    std::cout << "Calling fooImpl4() polymorphically. (Should not print FImplFinal::fooImpl4() or EImpl::fooImpl4())\n";
    impl::fooImpl4();
  

  void fooImpl3()
  
    std::cout << "FImpl::fooImpl3(), i = " << i << '\n';
  
;

struct F : FImpl<F>

;

int main()

  std::cout << "### A ###\n";
  A a;
  a.foo();
  a.foo2();
  a.foo3();
  a.foo4();

  std::cout << "### B ###\n";
  B b;
  b.foo();
  b.foo2();
  b.foo3();
  b.foo4();

  std::cout << "### C ###\n";
  C c;
  c.foo();
  c.foo2();
  c.foo3();
  c.foo4();

  std::cout << "### D ###\n";
  D d;
  d.foo();
  d.foo2();
  d.foo3();
  d.foo4();

  std::cout << "### E ###\n";
  E e;
  e.foo();
  e.foo2();
  e.foo3();
  e.foo4();

  std::cout << "### F ###\n";
  F f;
  f.foo();
  f.foo2();
  f.foo3();
  f.foo4();

代码打印:

### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### E ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
E::fooImpl3()
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### F ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
F::fooImpl2()
Attempting to call FFinal::fooImpl4() or E::fooImpl4()
DImplFinal::fooImpl4()
Calling CrtpInterface::foo3()
FImpl::fooImpl3(), i = 99
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()

【讨论】:

【参考方案4】:
template<class Derived>
struct A 
  void foo() 
    static_cast<Derived*>(this)->foo();
  
;

template<class Derived>
struct B: A <Derived> 
  void foo() 
    // do something
  
;

struct C: B <C> 
  void foo(); // can be either present or absent
;

如果 C 中的 foo() 不存在,则将调用 B 中的 foo()。否则将覆盖 B 中的那个。

【讨论】:

好主意:使 B 派生自 A&lt;Derived&gt; 而不是 A&lt;B&lt;Derived&gt;&gt;(在 CRTP 中通常如此)似乎可以节省一些样板代码,因为来自 A 的调用直接转到最派生类和编译器向上层级查找最深的foo。 (与my solution 相比,我们有时会下降一个级别。)话虽如此,但还是有一个问题:C c; B&lt;C&gt;&amp; b = c; b.foo(); 调用B::foo 即使b 的最衍生类型是C,这是一种转移从多态性会做什么。无论如何 +1!【参考方案5】:

多级继承不是问题,而是 CRTP 如何创建多态性。

template<typename Derived>
struct Base

    void f()  /* Basic case */ 

    // "Pure virtual" method
    void pure()  static_cast<Derived*>(this)->pure(); 
;

struct Overriding: Base<Overriding>

    void f()  /* Special case */ 

    // This method must exists to prevent endless recursion in Base::f
    void pure()  /* ... */ 
;

struct NonOverriding: Base<NonOverriding>

    void pure()  /* ... */ 
;

template<typename Derived>
void f(const Base<Derived>& base)

    base.f();    // Base::f
    base.pure(); // Base::pure, which eventually calls Derived::pure

    // Derived::f if an overriding method exists.
    // Base::f otherwise.
    static_cast<const Derived&>(base).f();

也可以引入derived 方法以避免在每次调用时进行冗长的类型转换。

template<typename Derived>
struct Base

    Derived& derived()  return *static_cast<Derived*>(this); 
    const Derived& derived() const  return *static_cast<const Derived*>(this); 
;

【讨论】:

【参考方案6】:

这是一个可能的实现,可以减少类内的样板代码(但不能减少辅助代码的总量)。

这个解决方案的思想是使用 SFINAE 和重载来选择 impl 函数。

(i) A 类

template <typename T> class A 
   void foo() const 
    static_cast<const T*>(this)->foo( Type2Type<T> );
   
 

其中 TypetoType 是模板结构

template< typename T > struct Type2Type 
  typedef T OriginalType

这对于帮助编译器选择 foo() 实现很有用。通过重载。

(i) B 类

template <typename T = void>
class B : public A<B<T>>  

 void foo(...)  
   std::cout << "B::foo()\n";  
 
 void foo( Type2Type< std::enable_if< is_base_of<T,B>::value == true, B>::type> )  
   static_cast<const T*>(this)->foo( Type2Type<T> );
 

这里,如果层次结构的底部由 C 给出,则不需要 TypetoType 参数。

(ii) C 类

class C : public B<C>  
 void foo(...)  
   std::cout << "C::foo()\n";  
 

我知道如果 T==B,std::is_base_of 返回 true。在这里,我们使用我们自己的 is_base_of 版本,它在两个模板参数相同时返回 false_type。像

template<class B, class D>
struct is_base_of : public is_convertible<D *, B *> ;
template<class B, class B>
struct is_base_of<B, B> :  public false_type ;
template<class D>
struct is_base_of<void, D> : public false_type ;

结论:如果 std::enable_if 失败,那么 foo() 的可变参数版本将是唯一可用的(因为 SFINAE)并且编译器将实现 B 版本的 foo。但是,如果 enable_if 没有失败,编译器将选择第二个版本(因为当编译器试图找出重载函数之间的最佳匹配时,可变参数是最后一个选项)。

【讨论】:

【参考方案7】:

在这个线程中有很多我觉得没有用的东西,所以我在这里分享我自己解决这个问题的方法。

CRTP 主要是一种代码缩减模式。为了正常工作,有必要在继承层次结构的每一层,都能够从下面的层调用所有函数——就像在通常的动态继承中一样。

但是,在 CRTP 中,每个阶段都必须进一步了解当前派生自它的最终类型,因为最终这就是 CRTP 的全部意义所在——调用始终适用于当前(静态)的函数最终类型。

可以通过在静态继承层次结构的每一层添加一个间接层来实现这一点,如下例所示:

template<typename derived_t>
struct level0_impl

    auto const& derived() const
    
        return static_cast<derived_t const&>(*this);
    
;
struct level0 : public level0_impl<level0>

    using level0_impl<level0>::level0_impl;
;


template<typename derived_t>
struct level1_impl : level0_impl<derived_t>

    auto only_for_level1_and_derived() const
    
        return derived().foo;
    ;
    auto do_something() const  std::cout<<"hi"<<std::endl; 
;
struct level1 : public level1_impl<level1>

    using level1_impl<level1>::level1_impl;
;


template<typename derived_t>
struct level2_impl : public level1_impl<derived_t>

    auto only_for_level2_and_derived() const
    
        return derived().bar;
    ;
;
struct level2 : public level2_impl<level2>

    using level2_impl<level2>::level2_impl;
;

// ... and so on ...

可以将它与 final 类型一起使用,如下所示:

#include <iostream>
struct final : public level2_impl<final>

    int foo = 1;
    double bar = 2.0;
;

int main()

    final f;
    std::cout<< f.only_for_level1_and_derived() <<std::endl;   //prints variable foo = 1
    std::cout<< f.only_for_level2_and_derived() <<std::endl;   //prints variable bar = 2.0

或者,您可以通过删除_impl 后缀来单独使用每个级别:

level1.do_something();   //prints "hi"

这是一件好事,尤其是不适用于该线程中的其他方法,例如

template<typename T> class A  auto& derived() return static_cast<T&>(*this); ;
template<typename T> class B : A<B<T> > ;
template<typename T> class C : B<C> ;    //here derived() in the base class does 
                                           //not return C, but B<C> -- which is
                                           //not what one usually wants in CRTP

【讨论】:

以上是关于CRTP 和多级继承的主要内容,如果未能解决你的问题,请参考以下文章

CRTP 中间类也需要成为 final

在多级和多级继承中使用 super()

如何使用类实现多级继承

多级继承

实体框架+多级继承+EF代码优先

Javascript“OOP”和具有多级继承的原型