在使用Eigen3库时,我遇到了问题。这是我的代码的非常简化的版本:
#include <eigen3/Eigen/Dense>
#include <iostream>
using MyVec = Eigen::Matrix<double, 2, 1>;
using MyMat = Eigen::Matrix<double, 2, 2>;
class Foo {
public:
Foo() = default;
Foo(const MyVec &mean) : mean_(mean) { }
Foo& operator+=(const MyVec& vec)
{
mean_ += vec;
return *this;
}
friend const Foo operator+(Foo lhs, const MyVec& rhs)
{
lhs += rhs;
return lhs;
}
private:
MyVec mean_;
};
int main(){
MyMat R(2*MyMat::Identity()),
P(10*MyMat::Identity()),
H(MyMat::Identity());
MyVec residual, x_vec;
x_vec << 2, 3;
Foo x_pred(x_vec);
residual << 0.1, 0.1;
const auto S_inv = (H*P*H.transpose().eval() + R).inverse();
const auto K = P*H*S_inv;
std::cout << "K = " << K << std::endl;
x_pred + K*residual;
std::cout << "K = " << K << std::endl;
return 0;
}
程序的输出如下:
K = 0.833333 0
0 0.833333
K = 0.00694444 0
0.00694444 0.833333
很显然,即使没有人更改变量,即使声明了x_pred + K*residual
,K的值也会在表达式const
之后改变。
我进行了以下其他测试,以找出这种现象的原因:
MyMat
作为变量K
的类型; S_inv
的初始化中将K
替换为(H*P*H.transpose().eval() + R).inverse()
; x_pred + K*residual
更改表达式K*residual
; x_pred + K*residual
更改表达式x_vec + K*residual;
并且它们都没有给我同样的意外行为(即变量K
的值不变)。
当然,我可以采用其中之一作为解决方案,但是我很好奇为什么会这样。有人知道吗?
我正在使用特征3.2.0-8和g ++-4.9.4作为编译器。
非常感谢。
编辑
我试图用g ++-4.7.3,clang ++-3.6.0和clang ++-3.9.1编译相同的代码;对于所有这些代码,它们均按预期工作(K
不变)。
但是,如果我升级到g ++的较新版本(我尝试了g ++-4.9.4,g ++-5.4.1和g ++-7.2.0),这种奇怪的行为仍然存在。
那可能是一些编译器优化吗?
答案 0 :(得分:2)
简短的回答:除非您真的知道自己在做什么(https://eigen.tuxfamily.org/dox/TopicPitfalls.html,否则请避免将auto
与本征表达式一起使用。只需写:
const MyMat S_inv = (H*P*H.transpose() + R).inverse(); // .eval() really did not help here
const MyMat K = P*H*S_inv;
使用.inverse()
代替线性系统实际上很好,因为对于2x2矩阵有一个明确的公式。
更多细节:下一行
const auto S_inv = (H*P*H.transpose().eval() + R).inverse();
等效于:
const auto S_inv = ((H*P)*(H.transpose().eval()) + R).inverse();
和auto
将是一些本征表达模板(不同,取决于本征的版本)。
现在H.transpose().eval()
将创建一个临时文件,该临时文件将在行尾被破坏,但仍由表达式S_inv
引用(再次从K
引用)。未定义的行为,即您可能会或可能不会获得正确的结果,但是您的程序也可能会崩溃。
如果删除.eval()
,则表达式实际上应在Eigen 3.3或更高版本上起作用(我认为)。使用auto
仍然不是一个好主意,因为每次使用表达式都会对其进行重新求值。