表达式后特征对象值意外更改

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。 我做了以下额外的测试,试图找出这种行为的原因:

和 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 仍然不是一个好主意,因为每次使用表达式时都会重新计算它们。