Reputation: 4721
I would like to know how data structures are managed in Eigen.
To be more exact, I would like to know if I should pass them as pointers, or perhaps they actually use some smart pointers.
For example, would the following function make sense:
SparseVector<double> retVector()
{
SparseVector<Double> vec(3);
return vec;
}
or would it be problematic because vec is a local variable, and SparseVector is not just a smart pointer wrapper class around vec?
Meaning, perhaps it is better to use:
SparseVector<double>* retVector()
{
SparseVector<Double> *vec = new SparseVector<double>(3);
return vec;
}
Upvotes: 0
Views: 212
Reputation: 10596
It's more complicated than that. If we were talking about any object, @Anycorn is correct in that the unoptimized version will make a deep copy of object in the function upon return. However, with optimizations enabled copy elision allows the compiler to construct the returned object in place. This is not specific to Eigen. As an example, we can look at a function similar to yours that returns an Eigen object.
Eigen::MatrixXd retMat()
{
Eigen::MatrixXd inMat = Eigen::MatrixXd::Random(1000,1000);
std::cout << &inMat << "\t" << inMat.data() << "\t" << *(inMat.data() + rand() * 20) << "\n";
return inMat;
}
Eigen::SparseMatrix<double> retSMat()
{
Eigen::MatrixXd denseMat = Eigen::MatrixXd::Random(100,100);
Eigen::SparseMatrix<double> inMat = denseMat.sparseView();
std::cout << &inMat << "\t" << "\n";
return inMat;
}
int main(int argc, char *argv[])
{
srand(time(NULL));
Eigen::MatrixXd outMat(5,5);
std::cout << "Dense matrix addresses (1):\n";
std::cout << &outMat << "\t" << outMat.data() << "\n";
outMat = retMat();
std::cout << &outMat << "\t" << outMat.data() << "\n\n";
std::cout << "Dense matrix addresses (2):\n";
Eigen::MatrixXd outMat2 = retMat();
std::cout << &outMat2 << "\t" << outMat2.data() << "\n\n";
std::cout << "Sparse matrix addresses:\n";
Eigen::SparseMatrix<double> outMatSp = retSMat();
std::cout << &outMatSp << "\n";
return 0;
}
Run in Debug (optimizations turned off) we get:
Dense matrix addresses (1):
00000032B6A8EF58 00000032B6BDB870
00000032B6A8EEA8 00000032B6CDA070 -0.620289
00000032B6A8EF58 00000032B6CDD070
Dense matrix addresses (2):
00000032B6A8EEA8 00000032B7481070 0.157872
00000032B6A8EF88 00000032B7C3F070
Sparse matrix addresses:
00000032B6A8EE60
00000032B6A8EFC0
We see that none of the addresses are identical, as would be expected by a naive interpretation of the code. If optimizations are turned on, the picture is a little different:
Dense matrix addresses (1):
0000009BEB0AF700 0000009BEB14BBF0
0000009BEB0AF780 0000009BEB2C1040 0.862606
0000009BEB0AF700 0000009BEBA7B040
Dense matrix addresses (2):
0000009BEB0AF718 0000009BEB2CC040 -0.601367
0000009BEB0AF718 0000009BEB2CC040
Sparse matrix addresses:
0000009BEB0AF740
0000009BEB0AF740
We see that in case (2) and the sparse matrix example, the addresses of the objects are identical, indicating that the object was created in the target objects address. Note that this is different than move semantics, which require a written move constructor (I'm pretty sure Eigen does not yet have an implemented one).
Regarding Eigen, due to its lazy evaluation, some expressions are not evaluated immediately, but rather when deemed prudent/necessary. This is is not the case in the simple example, but can be if the example had some calculations in it. In such a case, the returned object might be an expression tree that is appended/evaluated in the resulting object.
Upvotes: 1