C++实现MatrixX矩阵类行赋值功能:如何让m.row(i)支持左值赋值?
要实现 m.row(i) 可以放在赋值语句左侧的功能,最常用的两种实现思路如下:
方案1:实现行代理类(兼容C++11及以上版本,业内通用方案)
核心逻辑是让row()方法不直接返回行向量的拷贝,而是返回一个持有原矩阵引用和行索引的代理对象,通过重载代理对象的赋值、算术运算符,直接修改原矩阵对应行的内存数据,Eigen等成熟数值库都采用了这种实现。
代理类的核心实现示例如下:
template<typename Scalar> class MatrixX { private: std::vector<Scalar> data; size_t rows = 0, cols = 0; public: // 行代理类定义 class RowProxy { friend class MatrixX; MatrixX& parent; size_t row_idx; // 构造函数私有,仅允许MatrixX创建代理对象 RowProxy(MatrixX& mat, size_t idx) : parent(mat), row_idx(idx) {} public: // 支持初始化列表赋值:对应 m.row(i) = {{1,2,3}} 场景 RowProxy& operator=(std::initializer_list<Scalar> val_list) { if (val_list.size() != parent.cols) { throw std::invalid_argument("赋值元素数量与矩阵列数不匹配"); } size_t col = 0; for (Scalar val : val_list) { parent.data[row_idx * parent.cols + col] = val; col++; } return *this; } // 支持代理对象之间赋值:对应 m.row(j) = m.row(i) 场景 RowProxy& operator=(const RowProxy& other) { if (parent.cols != other.parent.cols) { throw std::invalid_argument("两行对应的矩阵列数不匹配"); } for (size_t c = 0; c < parent.cols; c++) { parent.data[row_idx * parent.cols + c] = other.parent.data[other.row_idx * other.parent.cols + c]; } return *this; } // 重载算术运算符:对应 m.row(j) = m.row(j) - 2*m.row(i) 场景 template<typename T> RowProxy& operator-=(const T& rhs) { for (size_t c = 0; c < parent.cols; c++) { parent.data[row_idx * parent.cols + c] -= rhs[c]; } return *this; } // 可按需扩展重载 +=、*=、/= 以及对应的全局算术运算符 // 重载下标运算符支持行内元素访问 Scalar& operator[](size_t col) { return parent.data[row_idx * parent.cols + col]; } const Scalar& operator[](size_t col) const { return parent.data[row_idx * parent.cols + col]; } size_t size() const { return parent.cols; } }; // row()方法返回代理对象 RowProxy row(size_t idx) { if (idx >= rows) throw std::out_of_range("行索引超出矩阵范围"); return RowProxy(*this, idx); } // const版本的row()方法返回只读代理,防止修改const矩阵 const RowProxy row(size_t idx) const { if (idx >= rows) throw std::out_of_range("行索引超出矩阵范围"); return RowProxy(const_cast<MatrixX&>(*this), idx); } // 其余MatrixX类实现省略 };
方案2:使用std::span(仅兼容C++20及以上版本,代码量极小)
如果你不需要兼容C++20以下的编译环境,可以直接让row()方法返回std::span,这是标准库提供的连续内存视图类型,原生支持赋值、下标访问等操作,不需要自己实现代理类:
#include <span> template<typename Scalar> class MatrixX { private: std::vector<Scalar> data; size_t rows = 0, cols = 0; public: std::span<Scalar> row(size_t idx) { if (idx >= rows) throw std::out_of_range("行索引超出矩阵范围"); return {data.data() + idx * cols, cols}; } std::span<const Scalar> row(size_t idx) const { if (idx >= rows) throw std::out_of_range("行索引超出矩阵范围"); return {data.data() + idx * cols, cols}; } // 其余MatrixX类实现省略 };
额外注意事项
- 所有操作前做好参数合法性校验,避免越界访问内存
- 代理对象/span持有原矩阵的引用,不要让其生命周期超过原矩阵,否则会出现悬空引用问题
- 如果需要支持更复杂的行运算表达式、避免中间拷贝,可以进一步引入表达式模板技术,不过实现复杂度会大幅提升,基础数值库使用代理类方案已经可以满足需求。
内容的提问来源于stack exchange,提问作者Quasar
相关产品推荐
相关产品推荐

