表达式后,特征对象值发生意外变化

时间:2018-07-26 14:35:43

标签: c++11 eigen3

在使用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),这种奇怪的行为仍然存在。 那可能是一些编译器优化吗?

1 个答案:

答案 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仍然不是一个好主意,因为每次使用表达式都会对其进行重新求值。