29

我的一个朋友问我“如何使用 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 不是多态性的直接替代品,但我们对两种模式都可以使用的情况感兴趣。(为了这个问题,我们对每种模式的优缺点不感兴趣。)

  1. 之前有人问过这个问题,但事实证明作者想要实现命名参数习语,他自己的答案更多地关注这个问题而不是 CRTP。另一方面,投票最多的答案似乎只是派生类方法在基类中调用其同音词。

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

4

7 回答 7

17

(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<T>::foo()从某种意义上说,它的行为类似于纯虚方法,它没有“默认实现”并且调用被定向到派生类。但是,这并不妨碍A<T>被实例化为非基类。为了得到这个行为A<T>::~A()是做出来的protected

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

protected:
    ~A() {}

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

A<T>当应该允许的实例化并且A<T>::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<>成为层次结构中最派生的对象,并使其合法:

B<> b;
b.foo();

另请注意,在 if是最派生类(或更准确地说, if is )B<T>::foo()的意义上,它表现为非纯虚方法,然后调用“默认实现”(输出)。如果不是,则调用将定向到派生类。这是通过标签调度完成的。B<T>Tvoidb.foo(); foo()B::foo()Tvoid

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

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

class C : public B<C> {

public:

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

};

或者,C::foo()如果继承的实现 ( B<C>::foo()) 足够,则可以完全删除。

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

(4)另见:

使用 CRTP 时如何避免错误?

于 2013-08-11T17:02:51.103 回答
13

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

编辑:我在这里发布了最终覆盖问题的解决方案

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

问题:

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

经典 CRTP 单级继承

解决方案:

将模式分为三个概念:

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

这是一个帮助说明的图表:

CRTP 的多级继承

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

使用这种方法,您可以:

  1. 无限期继承实现,
  2. 从任何级别调用 CRTP 多级继承链中实现的最高方法,
  3. 以与层次结构无关的方式设计每个实现,
  4. 忘记必须使用样板代码(好吧,反正不超过经典的单级 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()
      : i{1}
    {
    }

  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)
      : i{x}
    {
    }

  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 类层次结构所需的开销相对较小!

于 2016-08-30T20:37:59.640 回答
4

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

问题:

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

解决方案:

将界面拆分为三个概念:

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

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

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

使用这种方法:

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

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

DImpl 和 EImpl 具有最终函数,当 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()
    : i{1}
  {
  }

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)
      : i{x}
    {
    }

  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() 
    : i{99} 
  {
  }

  // 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()
于 2016-09-01T21:18:49.700 回答
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 中的那个将被覆盖。

于 2017-04-21T18:49:13.143 回答
1

多级继承不是问题,而是 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); }
};
于 2016-10-01T17:08:28.577 回答
0

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

这个解决方案的想法是使用 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 没有失败,编译器将选择第二个版本(因为当编译器试图找出重载函数之间的最佳匹配时,可变参数是最后一个选项)。

于 2013-08-11T23:10:16.677 回答
0

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

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 ...

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

#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
于 2019-02-05T22:13:19.343 回答