本期重點介紹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繼承體系如下圖所示
可以看出,在Matrix的繼承體系中,直接基類和Matrix使用了CRTP模式,Matrix將自己作為參數傳入到繼承的基類中。Matrix的基類本身也是繼承了基類,如MatrixBase繼承了DenseBase,然而MatrixBase和DenseBase並未使用
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具有拓展派生類實現行為的能力,而不是保持基類實現行為不變。
參考資料
- <<C++ Templates>> second edition. David Vandevoorde, Nicolai M. Josuttis and Douglas Gregor