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继承体系如下图所示
可以看出,在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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了