cast from Eigen::CwiseBinaryOp to MatrixXd causes

2019-09-18 15:35发布

问题:

I am writing a library that stores Eigen expression templates as member variables to do the complicated calculations it needs to do. However, it seems like I'm not able to store or return these expression templates unless they are directly converted in MatrixXd or alike. This forces every step to be saved to a temporary, and ruins the efficiency of the whole design.

Here's a short example that causes the trouble. Holder just holds an Eigen matrix, and Summer takes two holders and outputs the sum of the two matrices they hold when you call get(). The test that follows fails (segfault or std::bad_alloc) when the sum expression template is evaluated into a matrix.

Include file

#ifndef PB_SIMPLE_H
#define PB_SIMPLE_H

#include <Eigen/Dense>

template <class EigenType>
class Holder {
   public:
    typedef EigenType result_type;

   private:
    result_type in_;

   public:
    Holder(const EigenType& in) : in_(in) {}
    result_type get() const { return in_; }
};

template <class HoldLeft, class HoldRight>
class Summer {
   public:
    typedef const typename Eigen::CwiseBinaryOp<
        Eigen::internal::scalar_sum_op<double>,
        const typename HoldLeft::result_type,
        const typename HoldRight::result_type> result_type;
    // typedef Eigen::MatrixXd result_type;
   private:
    HoldLeft left_;
    HoldRight right_;

   public:
    Summer(const HoldLeft& left, const HoldRight& right)
        : left_(left), right_(right) {}

    result_type get() const { return left_.get() + right_.get(); }
};

typedef Holder<Eigen::MatrixXd> MatrixHolder;
typedef Summer<MatrixHolder, MatrixHolder> MatrixSummer;

#endif /* PB_SIMPLE_H */

simple test

#include "PbSimple.h"

#include <Eigen/Dense>

int main(int, char * []) {
  const unsigned int szx=10,szy=3;
  Eigen::MatrixXd x(Eigen::MatrixXd::Constant(szx,szy,1));
  MatrixHolder vx(x);
  Eigen::MatrixXd y(Eigen::MatrixXd::Constant(szx,szy,2));
  MatrixHolder vy(y);
  MatrixSummer vsum(vx,vy);
  auto expr = vsum.get();
  MatrixHolder vz(expr); //force evaluation of sum into new matrix, fails here
  return 0;
}
  • In the include file, if you use the commented out typedef instead, it works fine.
  • I suspect the problem is due to a dangling reference, but can't prove it.

回答1:

This is because Holder::get returns a copy of the matrix as a temporary. Then this temporary is stored as a const reference by the CWiseBinaryOp object returned by Summer::get, then this temporary is deleted, and finally when expr gets evaluated, expr is referencing deleted objects. You can fix this issue by making Holder::get returns a const reference to the matrix.