理解C++标准库[STL]的type_traits的实现原理,如何在实际开发中使用类型特性做算法优化。
背景
-
【问题实例】我们先看一个问题,对于n阶矩阵如何计算它的m次幂?
学过线性代数的朋友都知道,A^m = A*A*A*...*A
(一共m个A相乘)。
n阶矩阵A*A的时间复杂度为O(n*n*n)
;而A^m次方的复杂度为O(n*n*n)^m = O(n^3(m-1))
;这个复杂度有点高,对于具有特殊属性的矩阵,比如实对称矩阵,这种情况下,复杂度是否可以降下来呢,当然可以,我们可以用矩阵的相似对角化将矩阵A分解为A = inv(P)*Diag*P
;此时A^m = inv(P)*Diag^m*P
的复杂度可以降低到O(n^7*m)
; [这块复杂度分析不正确,读者可以大概理解下我想表达的意思:根据类型特性确实可以起到算法优化的目的] -
【解决思路】
1. 通用解法:老老实实按照矩阵乘法算, 适用于任何n阶矩阵,缺点效率低下
2. 高效解法:对于实对称矩阵,可以相似对角化; 优点:可以针对性优化,效率贼高 -
【C++解决方案】 模板编程:编译时候多态,效率高且对程序员友好; 如果用动多态技术的话,运行时效率会有问题,对于数值问题,不可取。
型别特性
type trait
这里的type
可以简单等同为class
,但二者并不完全一致,type
侧重接口,而class
侧重实现。这里不做区分。type_trait == class_trait
。- 在进入实例之前我们先建立一个概念,
type traits
或叫型别特性是什么意思,我们看下C++之父的一段话:
Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine “policy” or “implementation details”.
- Bjarne Stroustrup
- Bjarne Stroustrup大神说,型别特性是一个小对象,其主要目的是携带关于该型别的特性信息,这些信息会被另外的对象或者算法使用,来决定对象或算法自己的策略或实现细节。
矩阵求幂解决方案
1. 定义两种类型,用于标识实对称矩阵和普通矩阵
// 型别类型定义,没有任何成员定义
// 普通矩阵型别
struct GeneralMatrixCategory_type {};
// 实对称矩阵型别
struct RealSymmetricMatrixCategory_type {};
2. 定义型别特性模板类,用于矩阵型别特性萃取
// 矩阵型别特性萃取类
template <typename Matrix>
struct matrix_traits {
// 这里有个小小契约,所有矩阵类型必须有矩阵所属型别类型定义 category_type
typedef typename Matrix::category_type value_type;
};
3. 定义两种类型矩阵
- 两种矩阵都具有类型重定义类型别名 category_type
- 为了可以和后续的算法pow搭配,任何用户自定义的矩阵类型都需要定义类型别名 category_type
template <typename Tp>
class RealSymmetricMatrix {
public:
// 具有 RealSymmetricMatrixCategory_typ 的型别
typedef RealSymmetricMatrixCategory_type category_type;
public:
RealSymmetricMatrix(std::size_t dim) : dim_(dim) { }
private:
Tp* buffer_ = nullptr;
std::size_t dim_;
};
template <typename Tp>
class GeneralMatrix {
public:
// 具有 GeneralMatrixCategory_type 的型别
typedef GeneralMatrixCategory_type category_type;
public:
GeneralMatrix(std::size_t dim) : dim_(dim) {}
private:
Tp* buffer_ = nullptr;
std::size_t dim_;
};
4. 两个辅助函数
- 这里为什么是两个?因为只用两种矩阵型别类型定义。
template <typename Matrix>
Matrix __pow(const Matrix &m, size_t n, GeneralMatrixCategory_type) {
std::cout << "通用版本被调用" << std::endl;
}
template <typename Matrix>
Matrix __pow(const Matrix &m, size_t n, RealSymmetricMatrixCategory_type) {
std::cout << "高效版本被调用" << std::endl;
}
5. 用户接口
/**
* 计算矩阵m的n次方.
*
* @tparam Tp : scalar<int double>
* @param m
* @param n
* @return
*/
template <typename Matrix> Matrix pow(const Matrix &m, size_t n) {
typedef typename matrix_traits<Matrix>::value_type value_type;
__pow(m, n, value_type()); // 这里创建了一个匿名对象,编译器会自动进行类型推倒,进而调用两个辅助函数中的一个。
}
展望
- 可以加入别的矩阵类型,如
struct SparseMatrixCategory_type {};
- 并引入继承关系
struct SparseMatrixCategory_type : public GeneralMatrixCategory_type {};
struct RealSymmetricMatrixCategory_type {};
- 对于这种情况,如果有新的矩阵类型定义,比如
template <typename Tp>
class SparseMatrix {
public:
// 具有 SparseMatrixCategory_type 的型别
typedef SparseMatrixCategory_type category_type;
public:
SparseMatrix(std::size_t dim) : dim_(dim) {}
private:
Tp* buffer_ = nullptr;
std::size_t dim_;
};
- 即使没有专门针对稀疏矩阵求幂的算法实现,pow也可以用最普通版本的算法来计算稀疏矩阵求幂。这是由于继承关系的原因。
说明
- 两个矩阵类型的定义并不完整,这里关注的不是类本身的定义,而是算法如何自动选择最高效的实现版本来计算矩阵的m次幂。