[踩坑] Eigen 惰性求值与生命周期

引入

做作业时遇到个诡异的问题:

1
2
3
4
5
6
SimplicialLLT<SparseMatrix<double>> solver;
solver.compute(H);
auto grad_g_flatten = flatten(grad_g);
auto delta_X_flatten = solver.solve(grad_g_flatten);
// Wrong! What the f**k?!
// auto delta_X_flatten = solver.solve(flatten(grad_g));

按照上面的写法,是正常运行的,而按照注释的写法,得到的就是非常大的数字,明显是不正确的。这两种方法看上去区别不是很大,那么问题出在哪里呢?

分析

看一下相关的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename Rhs>
inline const Solve<Derived, Rhs>
solve(const MatrixBase<Rhs>& b) const
{
eigen_assert(m_isInitialized && "Solver is not initialized.");
eigen_assert(derived().rows()==b.rows() && "solve(): invalid number of rows of the right hand side matrix b");
return Solve<Derived, Rhs>(derived(), b.derived());
}

inline auto flatten = [&](const Eigen::MatrixXd& A) {
Eigen::MatrixXd A_flatten = A.transpose();
A_flatten.resize(A.rows() * A.cols(), 1);
return A_flatten;
};

似乎是没什么问题,flatten(grad_g) 的值被 const MatrixBase<Rhs>& 常值引用,在 solve 函数内是不会失效的。于是继续深入。

1
2
3
Solve(const Decomposition &dec, const RhsType &rhs)
: m_dec(dec), m_rhs(rhs)
{}

到这里,flatten(grad_g) 的值还是没有失效的,但我们可以看到,solve 函数实际上并没有进行求解,只是构造了一个 Solve 对象。这是 Eigen 的懒惰求值特性,由于用了 auto 变量接受返回值,所以得到的还是 Solve 对象,这点在 IDE 的提示中也可以看到。

当后面使用 delta_X_flatten 的时候,由于它是一个引用了 flatten(grad_g) 临时对象的 Solve 对象,而 flatten(grad_g) 已经被析构了,便会造成悬垂引用,产生错误。

解决

解决方法有两个,一个是像上面一样,先把 flatten(grad_g) 赋值给 grad_g_flattengrad_g_flatten 的生命周期足够长,就不会悬垂引用了;另一个是指明 delta_X_flatten 的类型为 MatrixXd,这样在赋值的时候,就会触发求值,后面不再依赖这个临时变量,也就不会悬垂引用了。


[踩坑] Eigen 惰性求值与生命周期
http://xiao-h.com/2025/02/19/踩坑-Eigen惰性求值与生命周期/
作者
小H
发布于
2025年2月19日
许可协议