首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >CRTP与多级遗传

CRTP与多级遗传
EN

Stack Overflow用户
提问于 2013-08-11 17:02:51
回答 7查看 9.4K关注 0票数 31

我的一个朋友问我“如何使用CRTP来取代多级遗传中的多态性”。更确切地说,在这种情况下:

代码语言:javascript
复制
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. 我想出了一个答案(张贴在下面),其中有相当多的样板代码,我想知道是否有更简单的选择。
EN

回答 7

Stack Overflow用户

发布于 2013-08-11 17:02:51

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

代码语言:javascript
复制
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;会使特殊的成员函数公开。在这种情况下,应该使用

代码语言:javascript
复制
protected:
    ~A() {}

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

当应该允许A<T>的实例化并且A<T>::foo()的行为应该像一个非纯的虚拟方法时,那么A应该类似于下面的模板类B

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

代码语言:javascript
复制
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<>类型的对象在层次结构中派生最多,因此这是合法的:

代码语言:javascript
复制
B<> b;
b.foo();

还请注意,B<T>::foo()的行为是一个非纯虚拟方法,也就是说,如果B<T>是最派生的类(或者更准确地说,如果Tvoid),那么b.foo();调用“foo()的默认实现”(它输出B::foo())。如果T不是void,则调用指向派生类。这是通过标记分派来完成的。

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

(3)最后,层次结构的底部如下所示:

代码语言:javascript
复制
class C : public B<C> {

public:

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

};

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

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

(4)也见:

如何在使用CRTP时避免错误?

票数 18
EN

Stack Overflow用户

发布于 2016-08-30 20:37:59

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

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

最近,我了解到CRTP及其作为运行时多态性的静态替代的潜力。在搜索了一段时间之后,看看CRTP是否可以作为类似于“插入”的多态替换,这样您就可以使用多层继承等等,我不得不说,我很惊讶在任何地方都找不到合适的通用解决方案,如果没有样板,它可以无限期地扩展。毕竟,考虑到它的所有性能好处,为什么不尝试将CRTP作为多态的替代物呢?随后进行了一些调查,下面是我得出的结论:

问题:

典型的CRTP模式在CRTP接口和实现类之间创建了一个可访问性的“循环”。( CRTP接口类通过自身对模板参数类型的静态转换访问“基类”实现类,实现类从CRTP接口类继承公共接口。)当您创建一个具体的实现时,您将关闭循环,这使得很难从具体的实现类继承,因此从它派生的东西也以多态的方式运行。

经典CRTP单层继承

解决方案:

将模式分为三个概念:

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

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

使用CRTP的多级继承

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

通过这种方法,您可以:

  1. 无限期继承实现,
  2. 从任何级别调用CRTP多级继承链中的最高实现方法,
  3. 以分层不可知论的方式设计每个实现,
  4. 忘了必须使用样板代码(好吧,至少与经典的单层CRTP一样),

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

示例代码:

代码语言:javascript
复制
#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();
}

其中的指纹:

代码语言:javascript
复制
### 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类层次结构所需的开销相对较少!

票数 16
EN

Stack Overflow用户

发布于 2016-09-01 21:18:49

在意识到我的原始答案实际上并没有处理最终的覆盖问题之后,我想我应该补充一下。我想拿出一个“最终覆盖”的解决方案,以类似于我以前的答案。

问题:

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

解决方案:

将接口划分为三个概念:

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

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

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

采用这一办法:

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

这一切听起来都很复杂,所以我做了一个流程图,试图让事情更容易理解:

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

示例代码:

代码语言:javascript
复制
#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();
}

代码打印:

代码语言:javascript
复制
### 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()
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18174441

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档