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機制


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM