Eigen源码阅读之二:奇异递归模板模式 CRTP

本期重点介绍Eigen贯穿整个Library的设计方法奇异递归模板模式。

一、CRTP基本样式

This oddly named pattern refers to a general class of techniques that consists of passing a derived class as a template argument to one of its own base classes

派生类作为模板参数传给其的一个基类,如下两例所示

template<typename Derived>
class CuriousBase {
   …
};

class Curious : public CuriousBase<Curious> {
   …
};
template<typename Derived>
class CuriousBase {
    …
};

template<typename T>
class CuriousTemplate : public CuriousBase<CuriousTemplate<T>> {
    …
};

二、Eigen中的CRTP

我们首选来看Matrix的定义

template<typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
class Matrix
  : public PlainObjectBase<Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_> >

PlainObjectBase的定义如下

template<typename Derived>
class PlainObjectBase : public internal::dense_xpr_base<Derived>::type

xprHelper.h中,我们可以看到dense_xpr_base的定义,它的作用也就是用来确定Matrix是个矩阵还是向量。

template<typename Derived>
struct dense_xpr_base<Derived, MatrixXpr>
{
  typedef MatrixBase<Derived> type;
};

template<typename Derived>
struct dense_xpr_base<Derived, ArrayXpr>
{
  typedef ArrayBase<Derived> type;
};

如果我们以传递的矩阵为例,那么PlainObjectBase就变为如下

template<typename Derived>
class PlainObjectBase : public MatrixBase<Derived>

我们再紧接着MatrixBase往父类上走,会有如下

template<typename Derived> struct EigenBase

template<typename Derived>
class DenseCoeffsBase<Derived,ReadOnlyAccessors> : public EigenBase<Derived>


template<typename Derived> class DenseBase
  : public DenseCoeffsBase<Derived, internal::accessors_level<Derived>::value>


template<typename Derived> class MatrixBase
  : public DenseBase<Derived>

更直观的表示,Matrix继承体系如下图所示

graph TD
EigenBase -->DenseCoeffsBase
DenseCoeffsBase --> DenseBase
DenseBase --> MatrixBase
MatrixBase --> PlainObjectBase
PlainObjectBase

可以看出,在Matrix的继承体系中,直接基类和Matrix使用了CRTP模式,Matrix将自己作为参数传入到继承的基类中。Matrix的基类本身也是继承了基类,如MatrixBase继承了DenseBase,然而MatrixBaseDenseBase并未使用
CRTP模式。

除了Matrix之外,对于一些运算符也使用了CRTP模式,比如CwiseBinaryOp, 其定义如下

template<typename BinaryOp, typename LhsType, typename RhsType>
class CwiseBinaryOp :
  public CwiseBinaryOpImpl<
          BinaryOp, LhsType, RhsType,
          typename internal::cwise_promote_storage_type<typename internal::traits<LhsType>::StorageKind,
                                                        typename internal::traits<RhsType>::StorageKind,
                                                        BinaryOp>::ret>, internal::no_assignment_operator


template<typename BinaryOp, typename Lhs, typename Rhs, typename StorageKind>
class CwiseBinaryOpImpl
  : public internal::generic_xpr_base<CwiseBinaryOp<BinaryOp, Lhs, Rhs> >::type
{
public:
  typedef typename internal::generic_xpr_base<CwiseBinaryOp<BinaryOp, Lhs, Rhs> >::type Base;
};


template<typename Derived, typename XprKind>
struct generic_xpr_base<Derived, XprKind, Dense>
{
  typedef typename dense_xpr_base<Derived,XprKind>::type type;
};

其中,dense_xpr_base在上文中有提到,那么总结可以看到最终CwiseBinaryOp还是将自己传给了基类,满足CRTP定义。

三、CRTP使用讨论

3.1 实现原理

基类模板定义了一些接口(或称行为),但是实现却是通过派生类来实现,这样看起来像多态,然而这种依靠不同类型,绑定不同实现的机制是在编译期间就确定了。

我们查看一下EigenBase中的一些接口

//EigenBase中的一些接口

/** \returns a reference to the derived object */
  EIGEN_DEVICE_FUNC
  Derived& derived() { return *static_cast<Derived*>(this); }//获取派生类引用
  /** \returns a const reference to the derived object */
  EIGEN_DEVICE_FUNC
  const Derived& derived() const { return *static_cast<const Derived*>(this); }

  EIGEN_DEVICE_FUNC
  inline Derived& const_cast_derived() const
  { return *static_cast<Derived*>(const_cast<EigenBase*>(this)); }
  EIGEN_DEVICE_FUNC
  inline const Derived& const_derived() const
  { return *static_cast<const Derived*>(this); }

  /** \returns the number of rows. \sa cols(), RowsAtCompileTime */
  EIGEN_DEVICE_FUNC EIGEN_CONSTEXPR
  inline Index rows() const EIGEN_NOEXCEPT { return derived().rows(); }//通过派生类的实现来实现,实际上在PlaibObjectBase中有定义rows(),通过调用DenseStorage的rows实现的
  /** \returns the number of columns. \sa rows(), ColsAtCompileTime*/
  EIGEN_DEVICE_FUNC EIGEN_CONSTEXPR
  inline Index cols() const EIGEN_NOEXCEPT { return derived().cols(); }

发现cols()rows()这些是通过派生类间接实现的,那么在派生类中就不用再次定义这些接口了。基于此,我们发现Matrix中的接口集中是一些构造接口,并非是预想的那样,里面会有好多矩阵操作的接口。实际上,Eigen正是将矩阵的各种操作进行不断的抽象,才出现我们今天看到的这么多继承等级。除此之外,EigenBase等也可以单独当作模板使用。

3.2 与普通模板的区别

普通模板虽然也定义了行为,但是行为的实现是不可变的,唯一的区别是实例化的类型不同。CRTP实例化后,每个派生类别的实现却可以不同。

template<typename T>
class Base{
 void Process(){
}
};

class A{
}

class B{
}
template<typename Derived>
class Base{
 void Process(){
    Derived().ProcessImpl();
 }

Derived& Derived(){
 return *static_cast<Derived*>(this);
}
};

class A:public Base<A>
{
      
       void ProcessImpl(){
      }
}

class B:public Base<B>
{
      
       void ProcessImpl(){
      }
}

上述两个代码片段可以看出,相比比普通模板,CRTP具有拓展派生类实现行为的能力,而不是保持基类实现行为不变。


参考资料

  1. <<C++ Templates>> second edition. David Vandevoorde, Nicolai M. Josuttis and Douglas Gregor

下一篇:Eigen源码阅读之三:LazyEvaluation机制

原文链接: https://www.cnblogs.com/sing-lau/p/15941388.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    Eigen源码阅读之二:奇异递归模板模式 CRTP

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/188155

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月12日 下午2:02
下一篇 2023年2月12日 下午2:03

相关推荐