CRTP模式概述

CRTP(奇异模板递归,Curiously Recurring Template Pattern)模式实现了接口继承,但没有虚函数表的开销。所有的类型检查、函数生成全都在编译期完成。

缺点: IDE几乎没法做到正确跳转。

基础架构

fwd.hpp —— 类型萃取与基类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace pinocchio
{
// CRTP的基类,由一个空的 traits (类型萃取)和一个基本上是空的类NumericalBase组成
template<class C>
struct traits
{
};

// 所有数值(SE3群等)的基类
template<class Derived>
struct NumericalBase
{
// 子类必须实现Scalar,即指定是float还是double(或者也可以是float)
// 子类如果没有Scalar,编译期直接报错
// C++模板延迟实例化,惰性编译。traits<Derived>::Scalar目前并不存在
// 但是编译的时候没有就报错
typedef typename traits<Derived>::Scalar Scalar;
};
}

se3.hpp —— 入口文件与宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// se3 是由 se3-base 和 se3-tpl 拼起来的
// 用户只需要包含 se3.hpp

// 先不用管宏定义,这些其实是SE3必须萃取的类型
#define PINOCCHIO_SE3_TYPEDEF_GENERIC(Derived, TYPENAME) \
typedef TYPENAME traits<Derived>::Scalar Scalar; \
typedef TYPENAME traits<Derived>::AngularType AngularType; \
typedef TYPENAME traits<Derived>::LinearType LinearType; \
typedef TYPENAME traits<Derived>::AngularRef AngularRef; \
typedef TYPENAME traits<Derived>::LinearRef LinearRef; \
typedef TYPENAME traits<Derived>::ConstAngularRef ConstAngularRef; \
typedef TYPENAME traits<Derived>::ConstLinearRef ConstLinearRef; \
typedef TYPENAME traits<Derived>::ActionMatrixType ActionMatrixType; \
typedef TYPENAME traits<Derived>::HomogeneousMatrixType HomogeneousMatrixType; \
typedef TYPENAME traits<Derived>::PlainType PlainType; \
enum \
{ \
Options = traits<Derived>::Options, \
LINEAR = traits<Derived>::LINEAR, \
ANGULAR = traits<Derived>::ANGULAR \
}

#define PINOCCHIO_SE3_TYPEDEF_TPL(Derived) PINOCCHIO_SE3_TYPEDEF_GENERIC(Derived, typename)

#define PINOCCHIO_SE3_TYPEDEF(Derived) \
PINOCCHIO_SE3_TYPEDEF_GENERIC(Derived, PINOCCHIO_MACRO_EMPTY_ARG)

#include "pinocchio/spatial/se3-base.hpp"
#include "pinocchio/spatial/se3-tpl.hpp"

se3-base.hpp —— CRTP父类接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace pinocchio
{
// CRTP 父类的模板是子类,套娃
template<class Derived>
struct SE3Base : NumericalBase<Derived>
{
// 用了之前的宏定义
// 这里列出了什么类型,子类必须萃取什么类型
PINOCCHIO_SE3_TYPEDEF_TPL(Derived);

// 向下造型,父类转换子类
Derived & derived()
{
return *static_cast<Derived *>(this);
}
const Derived & derived() const
{
return *static_cast<const Derived *>(this);
}

// 这个时候父类其实不知道子类有没有实现 rotation_impl()
// 就是调用,如果子类没实现编译就会报错
// 起到强制子类实现接口的作用,只要继承 SE3Base,必须实现 rotation_impl()
ConstAngularRef rotation() const
{
return derived().rotation_impl();
}

}
}

se3-tpl.hpp —— 子类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
namespace pinocchio
{
// 必须特化 traits,至少要萃取 :
// NumericalBase的Scalar
// SE3Base里面宏定义的那些类型
// 缺了编译就报错
template<typename _Scalar, int _Options>
struct traits<SE3Tpl<_Scalar, _Options>>
{
enum
{
Options = _Options,
LINEAR = 0,
ANGULAR = 3
};
typedef _Scalar Scalar;
typedef Eigen::Matrix<Scalar, 3, 1, Options> Vector3;
typedef Eigen::Matrix<Scalar, 4, 1, Options> Vector4;
typedef Eigen::Matrix<Scalar, 6, 1, Options> Vector6;
typedef Eigen::Matrix<Scalar, 3, 3, Options> Matrix3;
typedef Eigen::Matrix<Scalar, 4, 4, Options> Matrix4;
typedef Eigen::Matrix<Scalar, 6, 6, Options> Matrix6;
typedef Matrix3 AngularType;
typedef typename PINOCCHIO_EIGEN_REF_TYPE(Matrix3) AngularRef;
typedef typename PINOCCHIO_EIGEN_REF_CONST_TYPE(Matrix3) ConstAngularRef;
typedef Vector3 LinearType;
typedef typename PINOCCHIO_EIGEN_REF_TYPE(Vector3) LinearRef;
typedef typename PINOCCHIO_EIGEN_REF_CONST_TYPE(Vector3) ConstLinearRef;
typedef Matrix6 ActionMatrixType;
typedef Matrix4 HomogeneousMatrixType;
typedef SE3Tpl<Scalar, Options> PlainType;
}; // traits SE3Tpl

template<typename _Scalar, int _Options>
struct SE3Tpl : public SE3Base<SE3Tpl<_Scalar, _Options>>
{
// 来自 eigen,重载 new /new [] /delete/delete [],保证字节对齐
// 不管 new 多少次,内存永远对齐,永远全速 SIMD(一条指令算4次float运算)
EIGEN_MAKE_ALIGNED_OPERATOR_NEW

PINOCCHIO_SE3_TYPEDEF_TPL(SE3Tpl);
typedef SE3Base<SE3Tpl<_Scalar, _Options>> Base;
typedef Eigen::Quaternion<Scalar, Options> Quaternion;
typedef typename traits<SE3Tpl>::Vector3 Vector3;
typedef typename traits<SE3Tpl>::Matrix3 Matrix3;
typedef typename traits<SE3Tpl>::Matrix4 Matrix4;
typedef typename traits<SE3Tpl>::Vector4 Vector4;
typedef typename traits<SE3Tpl>::Matrix6 Matrix6;

// 让子类继承并暴露父类的 rotation/translation 函数,使其可见、可调用、可被 IDE 识别
// 套娃,父类的 rotation 函数其实调用的是子类的 rotation_impl
// 接口在父类,子类负责实现
using Base::rotation;

ConstAngularRef rotation_impl() const
{
return rot;
}
AngularRef rotation_impl()
{
return rot;
}
void rotation_impl(const AngularType & R)
{
rot = R;
}
}

CRTP模式核心要点

  1. 模板延迟实例化(惰性编译) —— 父类声明时引用 traits<Derived>::Scalar,此时类型尚未定义,但不会报错。只有当模板被实际实例化时,编译器才会检查类型是否存在。

  2. 编译期类型检查 —— 子类如果没有正确特化 traits,编译期直接报错,起到强制接口约束的作用。

  3. 零运行时开销 —— 没有虚函数表,没有虚函数调用开销,所有调用在编译期确定,性能等同于普通函数调用。

  4. 套娃式继承 —— 子类 SE3Tpl 继承自父类 SE3Base<SE3Tpl>,父类模板参数就是子类自己。

  5. derived() 向下造型 —— 父类通过 static_cast 将 this 指针转换为子类指针,从而调用子类的 *_impl() 实现函数。