表达式后特征对象值意外更改
Eigen object value changes unexpectedly after expression
我在使用 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
显然,K 的值在表达式 x_pred + K*residual
之后发生变化,即使没有人更改变量,即使声明了变量 const
。
我做了以下额外的测试,试图找出这种行为的原因:
- 使用
MyMat
作为变量的类型 K
;
- 用表达式
(H*P*H.transpose().eval() + R).inverse()
; 替换 K
初始化中的 S_inv
- 将表达式
x_pred + K*residual
更改为 K*residual
;
- 将表达式
x_pred + K*residual
更改为 x_vec + K*residual;
和 none 给了我同样的意外行为(即变量 K
的值没有改变)。
当然,我可以采用其中之一作为解决方案,但我很想知道为什么会这样。有人知道吗?
我使用 eigen 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),这种奇怪的行为仍然存在。
那会不会是一些编译器优化?
简短回答:避免将 auto
与 Eigen 表达式一起使用,除非您真的知道自己在做什么 (https://eigen.tuxfamily.org/dox/TopicPitfalls.html)。而只是写:
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
会是一些Eigen表达式模板(不同,取决于Eigen的版本)。
现在 H.transpose().eval()
将创建一个临时对象,它将在行尾被破坏,但仍然从表达式 S_inv
中引用(再次从 K
中引用。这意味着未定义的行为,即,您可能会或可能不会得到正确的结果,但您的程序也可能会崩溃。
如果你删除 .eval()
你的表达式实际上应该适用于 Eigen 3.3 或更高版本(我认为)。使用 auto
仍然不是一个好主意,因为每次使用表达式时都会重新计算它们。
我在使用 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
显然,K 的值在表达式 x_pred + K*residual
之后发生变化,即使没有人更改变量,即使声明了变量 const
。
我做了以下额外的测试,试图找出这种行为的原因:
- 使用
MyMat
作为变量的类型K
; - 用表达式
(H*P*H.transpose().eval() + R).inverse()
; 替换 - 将表达式
x_pred + K*residual
更改为K*residual
; - 将表达式
x_pred + K*residual
更改为x_vec + K*residual;
K
初始化中的 S_inv
和 none 给了我同样的意外行为(即变量 K
的值没有改变)。
当然,我可以采用其中之一作为解决方案,但我很想知道为什么会这样。有人知道吗?
我使用 eigen 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),这种奇怪的行为仍然存在。
那会不会是一些编译器优化?
简短回答:避免将 auto
与 Eigen 表达式一起使用,除非您真的知道自己在做什么 (https://eigen.tuxfamily.org/dox/TopicPitfalls.html)。而只是写:
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
会是一些Eigen表达式模板(不同,取决于Eigen的版本)。
现在 H.transpose().eval()
将创建一个临时对象,它将在行尾被破坏,但仍然从表达式 S_inv
中引用(再次从 K
中引用。这意味着未定义的行为,即,您可能会或可能不会得到正确的结果,但您的程序也可能会崩溃。
如果你删除 .eval()
你的表达式实际上应该适用于 Eigen 3.3 或更高版本(我认为)。使用 auto
仍然不是一个好主意,因为每次使用表达式时都会重新计算它们。