目录
一、奇异递归模版(CRTP)
二、表达式模板
🍉 概要
🍇 奇异递归模板模式(CRTP)
动机与原理
🍓 表达式模板(Expression Templates)
动机与原理
🍈 示例代码
🍒 示例程序对应的CRTP和编译树
一、奇异递归模版(CRTP)
作用:编译期多态,在compile斌阿姨阶段确定调用子类接口实现的静态的能力
#include <cassert>
#include <iostream>
#include <vector>template <typename Derived>
struct Base
{void name() { (static_cast<Derived *>(this))->impl(); }
};
struct D1 : public Base<D1>
{void impl() { std::cout << "D1::impl" << std::endl; }
};
struct D2 : public Base<D2>
{void impl() { std::cout << "D2::impl" << std::endl; }
};// 主函数入口
int main()
{Base<D1> derived1;Base<D2> derived2;D1 derived3;D2 derived4;func(derived1);func(derived2);func(derived3);func(derived4);return 0;
}
二、表达式模板
🍉 概要
最近在学习Eigen 线性代数计算库的代码,对其中很多C++的模板定义不甚理解,作为一名资深码农甚是惭愧。对于解决一个向量加法表达式v3 = v0 + v1 + v2,传统的编程方式存在中间内存的申请与释放、多次循环遍历等空间和时间的低效问题,不符合Eigen的高性能计算需求,为了提升性能,Eigen用了很多模板元编程技术,本文总结了其中两个知识点
- 奇异递归模板模式(CRTP)
- 表达式模板(Expression Templates)
🍇 奇异递归模板模式(CRTP)
动机与原理
- 用父类提供统一的API 接口
- 在Compile编译阶段确定调用子类接口实现的静态多态能力(相对于运行线虚函数的动态多态提升了运行时性能)
- 实现原理如下图所示
🍓 表达式模板(Expression Templates)
动机与原理
- 延迟计算表达式,从而可以将表达式传递给函数参数,而不是只能传表达式的计算结果
- 节省表达式中间计算结果的临时存储空间,减少向量等线性代数计算的循环次数,从而减少整体计算的空间和时间成本
- 实现原理如下图所示
🍈 示例代码
上面的原理图看起来还是比较难以理解的,下面以求解一个向量加法赋值表达式:v3 = v0 + v1 + v2 的程序代码为例解析原理图中的涵义
#include <cassert>
#include <iostream>
#include <vector>//CRTP中的基类模板
template <typename E>
class VecExpression {
public://通过将自己static_cast成为子类,调用子类的对应函数实现实现静态多态double operator[](size_t i) const { return static_cast<E const&>(*this)[i]; }size_t size() const { return static_cast<E const&>(*this).size(); }};
//将自己作为基类模板参数的子类 - 对应表达式编译树中的叶节点
class Vec : public VecExpression<Vec> {std::vector<double> elems;public:double operator[](size_t i) const { return elems[i]; }double &operator[](size_t i) { return elems[i]; }size_t size() const { return elems.size(); }Vec(size_t n) : elems(n) {}Vec(std::initializer_list<double>init){for(auto i:init)elems.push_back(i);}//赋值构造函数可以接受任意父类VecExpression的实例,并且进行表达式的展开(对应表达式编译树中的赋值运算符节点)template <typename E>Vec(VecExpression<E> const& vec) : elems(vec.size()) {for (size_t i = 0; i != vec.size(); ++i) {elems[i] = vec[i];}}
};
//将自己作为基类模板参数的子类 - 对应表达式编译树中的二元运算符输出的内部节点
//该结构的巧妙之处在于模板参数E1 E2可以是VecSum,从而形成VecSum<VecSum<VecSum ... > > >的嵌套结构,体现了表达式模板的精髓:将表达式计算改造成为了构造嵌套结构
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {E1 const& _u;E2 const& _v;
public:VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {assert(u.size() == v.size());}double operator[](size_t i) const { return _u[i] + _v[i]; }size_t size() const { return _v.size(); }
};
//对应编译树上的二元运算符,将加法表达式构造为VecSum<VecSum... > >的嵌套结构
template <typename E1, typename E2>
VecSum<E1,E2> const operator+(E1 const& u, E2 const& v) {return VecSum<E1, E2>(u, v);
}
//主函数入口
int main() {//创建3个叶子节点Vec v0 = {1.0, 1.0, 1.0};Vec v1 = {2.0, 2.0, 2.0};Vec v2 = {3.0, 3.0, 3.0};//构建表达式 v0 + v1 + v2,赋值给v3,编译阶段形成表达式树Vec v3 = v0 + v1 + v2;//输出结算结果for (int i = 0; i < v3.size(); i ++) {std::cout <<" "<< v3[i];}std::cout << std::endl;
}
编译程序:
g++ -I /usr/local/include/eigen3/ -Wall -std=c++11 -fpermissive -g vecAdd.cpp -o vecAdd运行程序
./vecAdd程序输出:6 6 6