如何在C++11中安全转换Eigen稀疏矩阵的行/列主序引用?
解决Eigen稀疏矩阵存储序的类型安全转换问题
兄弟,你这里踩了Eigen的一个常见坑——IsRowMajor是编译时常量,不是运行时可以判断的变量!也就是说,你的if(M.IsRowMajor)在编译阶段就会被直接优化掉,要么永远走true分支,要么永远走false分支,完全取决于传入的M的实际类型。这也是直接转引用编译失败的核心原因:编译器明确知道M的类型是SparseMatrix<double>(默认列主序),你硬要转成行主序的引用,类型根本不匹配嘛!
下面给你几个实用的解决方案,既能保证类型安全,又能避免不必要的矩阵复制,还能让代码更简洁:
方案一:模板化顶层函数,利用编译时判断优化性能
既然存储序是编译时确定的,我们直接把sum_along_axis改成模板函数,让编译器在编译阶段就帮我们处理类型匹配的情况,完全不需要冗余的运行时判断:
#include <vector> #include <Eigen/Sparse> // 类型别名简化代码 typedef Eigen::SparseMatrix<double> CMSmat; // 默认列主序 typedef Eigen::SparseMatrix<double, Eigen::RowMajor> RMSmat; enum direction { row, col }; template<class Mat> std::vector<double> sum_along_inner(Mat &M){ std::vector<double> sums(M.outerSize(), 0); // 这里修正了你原代码的错误:应该用outerSize而非innerSize for(auto i = 0; i < M.outerSize(); i++){ for(typename Mat::InnerIterator it(M,i); it; ++it){ sums[i] += it.value(); } } return sums; // 原代码漏了return语句,补上! } // 模板化的sum_along_axis,支持任意存储序的稀疏矩阵 template <typename Smat> std::vector<double> sum_along_axis(Smat &M, direction dir) { if (dir == direction::row) { // 矩阵本身是行主序,直接传引用调用,无复制 if (Smat::IsRowMajor) { return sum_along_inner<Smat>(M); } else { // 列主序转行主序,必须复制(存储序不同无法直接引用) RMSmat M_row(M); return sum_along_inner<RMSmat>(M_row); } } else { // 按列求和 // 矩阵本身是列主序,直接传引用调用 if (!Smat::IsRowMajor) { return sum_along_inner<Smat>(M); } else { // 行主序转列主序,必须复制 CMSmat M_col(M); return sum_along_inner<CMSmat>(M_col); } } }
方案二:重载函数,统一接口同时避免模板复杂度
如果你因为项目规范或其他原因必须保持顶层函数的非模板接口,可以直接写两个重载函数,让编译器自动匹配矩阵类型:
// 处理列主序矩阵的重载 std::vector<double> sum_along_axis(CMSmat &M, direction dir) { if (dir == direction::row) { RMSmat M_row(M); return sum_along_inner<RMSmat>(M_row); } else { return sum_along_inner<CMSmat>(M); } } // 处理行主序矩阵的重载 std::vector<double> sum_along_axis(RMSmat &M, direction dir) { if (dir == direction::row) { return sum_along_inner<RMSmat>(M); } else { CMSmat M_col(M); return sum_along_inner<CMSmat>(M_col); } }
这种方式完全不需要手动做类型转换,编译器会自动根据传入的矩阵类型选择对应的重载,存储序匹配时直接传引用,性能拉满。
额外优化:让求和函数自动适配存储序
其实你还可以进一步简化代码,让sum_along_inner不需要关心矩阵存储序,直接根据求和方向处理:
template<class Mat> std::vector<double> sum_along_rows(Mat &M){ std::vector<double> sums(M.rows(), 0); // 不管存储序,直接遍历每一行 for(int i = 0; i < M.rows(); ++i){ for(typename Mat::InnerIterator it(M, i); it; ++it){ sums[i] += it.value(); } } return sums; } template<class Mat> std::vector<double> sum_along_cols(Mat &M){ std::vector<double> sums(M.cols(), 0); // 不管存储序,直接遍历每一列 for(int j = 0; j < M.cols(); ++j){ for(typename Mat::InnerIterator it(M, j); it; ++it){ sums[j] += it.value(); } } return sums; } // 此时sum_along_axis变得异常简洁 template <typename Smat> std::vector<double> sum_along_axis(Smat &M, direction dir) { return dir == direction::row ? sum_along_rows(M) : sum_along_cols(M); }
Eigen的InnerIterator会自动适配矩阵的存储序,不管是行主序还是列主序,都能正确遍历目标行/列的元素,完全不需要手动转换存储序,代码可读性和维护性都提升了一大截。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

