具有unique_ptr和矩阵的持久表达式模板



我想使用表达式模板来创建一个在语句中持久存在的对象树。构建树最初需要使用特征线性代数库进行一些计算。持久表达式模板将有额外的方法,通过以不同的方式遍历树来计算其他量(但我还没有做到)。

为了避免临时性超出范围的问题,子表达式对象通过std::unique_ptr进行管理。在构建表达式树时,指针应该向上传播,这样保持根对象的指针可以确保所有对象都处于活动状态。由于Eigen创建的表达式模板包含对语句末尾超出范围的临时变量的引用,因此情况变得复杂,因此在构建树时必须评估所有Eigen表达式。

下面是一个按比例缩小的实现,当val类型是一个包含整数的对象时,它似乎可以工作,但对于Matrix类型,它在构造output_xpr对象时会崩溃。崩溃的原因似乎是Eigen的矩阵乘积表达式模板(Eigen::GeneralProduct)在使用之前就已损坏。然而,在崩溃发生之前,我自己的表达式对象或GeneralProduct的析构函数似乎都没有被调用,valgrind也没有检测到任何无效的内存访问。

任何帮助都将不胜感激!我也很感激对我将move构造函数与静态继承一起使用的评论,也许问题就在某个地方。

#include <iostream>
#include <memory>
#include <Eigen/Core>
typedef Eigen::MatrixXi val;
// expression_ptr and derived_ptr: contain unique pointers
// to the actual expression objects
template<class Derived>
struct expression_ptr {
Derived &&transfer_cast() && {
return std::move(static_cast<Derived &&>(*this));
}
};
template<class A>
struct derived_ptr : public expression_ptr<derived_ptr<A>> {
derived_ptr(std::unique_ptr<A> &&p) : ptr_(std::move(p)) {}
derived_ptr(derived_ptr<A> &&o) : ptr_(std::move(o.ptr_)) {}
auto operator()() const {
return (*ptr_)();
}
private:
std::unique_ptr<A> ptr_;
};
// value_xpr, product_xpr and output_xpr: expression templates
// doing the actual work
template<class A>
struct value_xpr {
value_xpr(const A &v) : value_(v) {}
const A &operator()() const {
return value_;
}
private:
const A &value_;
};
template<class A,class B>
struct product_xpr {
product_xpr(expression_ptr<derived_ptr<A>> &&a, expression_ptr<derived_ptr<B>> &&b) :
a_(std::move(a).transfer_cast()), b_(std::move(b).transfer_cast()) {
}
auto operator()() const {
return a_() * b_();
}
private:
derived_ptr<A> a_;
derived_ptr<B> b_;
};
// Top-level expression with a matrix to hold the completely
// evaluated output of the Eigen calculations
template<class A>
struct output_xpr {
output_xpr(expression_ptr<derived_ptr<A>> &&a) :
a_(std::move(a).transfer_cast()), result_(a_()) {}
const val &operator()() const {
return result_;
}
private:
derived_ptr<A> a_;
val result_;
};
// helper functions to create the expressions
template<class A>
derived_ptr<value_xpr<A>> input(const A &a) {
return derived_ptr<value_xpr<A>>(std::make_unique<value_xpr<A>>(a));
}
template<class A,class B>
derived_ptr<product_xpr<A,B>> operator*(expression_ptr<derived_ptr<A>> &&a, expression_ptr<derived_ptr<B>> &&b) {
return derived_ptr<product_xpr<A,B>>(std::make_unique<product_xpr<A,B>>(std::move(a).transfer_cast(), std::move(b).transfer_cast()));
}
template<class A>
derived_ptr<output_xpr<A>> eval(expression_ptr<derived_ptr<A>> &&a) {
return derived_ptr<output_xpr<A>>(std::make_unique<output_xpr<A>>(std::move(a).transfer_cast()));
}
int main() {
Eigen::MatrixXi mat(2, 2);
mat << 1, 1, 0, 1;
val one(mat), two(mat);
auto xpr = eval(input(one) * input(two));
std::cout << xpr() << std::endl;
return 0;
}

您的问题似乎是使用了其他人的表达式模板,并将结果存储在auto中。

(这发生在product_xpr<A>::operator()中,在这里你称*,如果我读对了,它是一个使用表达式模板的特征乘法)。

表达式模板通常被设计为假设整个表达式将出现在一行上,并且它将以导致表达式模板求值的汇点类型(如矩阵)结束。

在您的案例中,您有a*b表达式模板,然后使用它来构造表达式模板返回值,稍后对其进行求值。在a*b中传递给*的临时值的生存期将在您到达接收器类型(矩阵)时结束,这违反了表达式模板的预期。

我正在努力想出一个解决方案,以确保所有临时对象的使用寿命都得到延长。我的一个想法是某种延续传递风格,而不是调用:

Matrix m = (a*b);

你做

auto x = { do (a*b) pass that to (cast to matrix) }

更换

auto operator()() const {
return a_() * b_();
}

带有

template<class F>
auto operator()(F&& f) const {
return std::forward<F>(f)(a_() * b_());
}

其中"下一步"被传递给每个子表达式。这对二进制表达式来说更为棘手,因为你必须确保第一个表达式的求值调用导致第二个子表达式求值的代码,然后将两个表达式组合在一起,所有这些都在同一个长递归调用堆栈中。

我对延续传递风格不够精通,无法完全解开这个结,但它在函数编程界有些流行。

另一种方法是将树扁平化为一个选项元组,然后使用一个花哨的运算符()构造树中的每个选项,并以这种方式手动连接参数。基本上对中间值进行手动内存管理。如果Eigen表达式模板是移动感知的或者没有任何自指针,那么这将起作用,这样在构建点移动就不会破坏东西。写这篇文章会很有挑战性。

Yakk提出的延续传递风格解决了这个问题,而且并不太疯狂(无论如何,并不比模板元编程更疯狂)。二进制表达式参数的双lambda求值可以隐藏在辅助函数中,请参阅下面代码中的binary_cont。作为参考,由于这并不完全是琐碎的,我在这里发布了固定的代码。

如果有人理解为什么我必须在binary_cont中的F类型上放置const限定符,请告诉我。

#include <iostream>
#include <memory>
#include <Eigen/Core>
typedef Eigen::MatrixXi val;
// expression_ptr and derived_ptr: contain unique pointers
// to the actual expression objects
template<class Derived>
struct expression_ptr {
Derived &&transfer_cast() && {
return std::move(static_cast<Derived &&>(*this));
}
};
template<class A>
struct derived_ptr : public expression_ptr<derived_ptr<A>> {
derived_ptr(std::unique_ptr<A> &&p) : ptr_(std::move(p)) {}
derived_ptr(derived_ptr<A> &&o) = default;
auto operator()() const {
return (*ptr_)();
}
template<class F>
auto operator()(F &&f) const {
return (*ptr_)(std::forward<F>(f));
}
private:
std::unique_ptr<A> ptr_;
};
template<class A,class B,class F>
auto binary_cont(const derived_ptr<A> &a_, const derived_ptr<B> &b_, const F &&f) {
return a_([&b_, f = std::forward<const F>(f)] (auto &&a) {
return b_([a = std::forward<decltype(a)>(a), f = std::forward<const F>(f)] (auto &&b) {
return std::forward<const F>(f)(std::forward<decltype(a)>(a), std::forward<decltype(b)>(b));
});
});
}
// value_xpr, product_xpr and output_xpr: expression templates
// doing the actual work
template<class A>
struct value_xpr {
value_xpr(const A &v) : value_(v) {}
template<class F>
auto operator()(F &&f) const {
return std::forward<F>(f)(value_);
}
private:
const A &value_;
};
template<class A,class B>
struct product_xpr {
product_xpr(expression_ptr<derived_ptr<A>> &&a, expression_ptr<derived_ptr<B>> &&b) :
a_(std::move(a).transfer_cast()), b_(std::move(b).transfer_cast()) {
}
template<class F>
auto operator()(F &&f) const {
return binary_cont(a_, b_,
[f = std::forward<F>(f)] (auto &&a, auto &&b) {
return f(std::forward<decltype(a)>(a) * std::forward<decltype(b)>(b));
});
}
private:
derived_ptr<A> a_;
derived_ptr<B> b_;
};
template<class A>
struct output_xpr {
output_xpr(expression_ptr<derived_ptr<A>> &&a) :
a_(std::move(a).transfer_cast()) {
a_([this] (auto &&x) { this->result_ = x; });
}
const val &operator()() const {
return result_;
}
private:
derived_ptr<A> a_;
val result_;
};
// helper functions to create the expressions
template<class A>
derived_ptr<value_xpr<A>> input(const A &a) {
return derived_ptr<value_xpr<A>>(std::make_unique<value_xpr<A>>(a));
}
template<class A,class B>
derived_ptr<product_xpr<A,B>> operator*(expression_ptr<derived_ptr<A>> &&a, expression_ptr<derived_ptr<B>> &&b) {
return derived_ptr<product_xpr<A,B>>(std::make_unique<product_xpr<A,B>>(std::move(a).transfer_cast(), std::move(b).transfer_cast()));
}
template<class A>
derived_ptr<output_xpr<A>> eval(expression_ptr<derived_ptr<A>> &&a) {
return derived_ptr<output_xpr<A>>(std::make_unique<output_xpr<A>>(std::move(a).transfer_cast()));
}
int main() {
Eigen::MatrixXi mat(2, 2);
mat << 1, 1, 0, 1;
val one(mat), two(mat), three(mat);
auto xpr = eval(input(one) * input(two) * input(one) * input(two));
std::cout << xpr() << std::endl;
return 0;
}

最新更新