最近在segmentfault.com上看到一个问题,提问者想要利用C++11的移动语义,减少矩阵相加时临时对象的构造。受此启发,我实现了一个简单的矩阵类,利用C++11标准中的一些特性,对矩阵运算进行了时间和空间效率的优化。(完整代码:matrix.h、matrix_test.cc、run.sh)
用C++11的移动语义,优化矩阵运算的空间效率
C++11的右值引用(T&&),可以将一个变量标记为“临时的”、“在此之后不再需要”,让该变量的使用者可以放心地将其内容“偷”走,而不用担心会有不良后果。利用该特性,可以减少矩阵运算中不必要的临时对象构造和拷贝,实现空间效率的优化。
矩阵加法、减法和数乘
先讨论矩阵加法。
常用的加法函数的声明格式为 T operator+(const T& lhs, const T& rhs); ,例如std::plus。这样符合我们对加法的常识,两个加数在相加前后都不变(不被修改),返回一个临时对象作为结果。这样做的代价是,每次加法都要构造一个临时对象。(这里有个插曲,返回值优化会在 T v = a1 + a2; 中省去一次临时对象构造,但在 T v = a1 + a2 + ... + an; 中仍然要进行n-1次临时对象的构造,临时对象构造不可避免)
而在矩阵运算中,构造临时矩阵代价就更大。因此,除了Matrix<T> operator+(const Matrix<T>& lhs, const Matrix<T>& rhs); 这种常用形式,可以引入另外两种声明形式:
Matrix<T> operator+(const Matrix& lhs, Matrix&& rhs);
Matrix<T> operator+(Matrix&& lhs, const Matrix& rhs);
在连续相加中产生的临时矩阵,匹配到右值引用参数,然后其空间被“偷”走,作为函数返回值传出。再加上返回值优化,就可以在整个加法表达式中不构造任何临时矩阵,全部实现原地运算,优化矩阵加法的空间效率。
实现代码如下:
template <typename T>
Matrix<T> operator+(const Matrix<T>& lhs, Matrix<T>&& rhs) {
matrix::CheckDimensionMatches(lhs, rhs);
Matrix<T> result(std::move(rhs));
result += lhs;
return result;
}
template <typename T>
Matrix<T> operator+(Matrix<T>&& lhs, const Matrix<T>& rhs) {
return rhs + std::move(lhs);
}