2

我想使用表达式模板来创建一个跨语句持续存在的对象树。构建树最初涉及使用 Eigen 线性代数库进行一些计算。持久表达式模板将具有其他方法来通过以不同方式遍历树来计算其他数量(但我还没有)。

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

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

任何帮助都感激不尽!我也很欣赏关于我将移动构造函数与静态继承一起使用的评论,也许问题出在某个地方。

#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;
}
4

2 回答 2

2

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

(这发生在product_xpr<A>::operator(),您调用*,如果我没看错的话,它是使用表达式模板的特征乘法)。

表达式模板通常被设计为假定整个表达式将出现在一行上,并且它将以导致表达式模板被评估的接收器类型(如矩阵)结束。

在您的情况下,您有a*b表达式模板,然后用于构造表达式模板返回值,您稍后会对其进行评估。传递给*in的临时对象的生命周期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_());
}

其中“下一步”被传递给每个子表达式。这对于二进制表达式变得更加棘手,因为您必须确保第一个表达式的评估调用导致第二个子表达式被评估的代码,然后是两个表达式组合在一起,都在同一个长递归调用堆栈中。

我对延续传递风格的熟练程度不足以完全解开这个结,但它在函数式编程世界中有点流行。

另一种方法是将你的树扁平化为一个可选的元组,然后使用一个花哨的 operator() 在树中构造每个可选,并以这种方式手动连接参数。基本上对中间值进行手动内存管理。如果 Eigen 表达式模板是移动感知的或没有任何自指针,这将起作用,因此在构造点移动不会破坏事物。写那将是具有挑战性的。

于 2015-04-16T15:59:19.980 回答
1

Yakk建议的延续传递风格解决了这个问题,而且不是太疯狂(无论如何,一般来说并不比模板元编程更疯狂)。二进制表达式参数的双重 lambda 评估可以隐藏在辅助函数中,见binary_cont下面的代码。作为参考,并且由于它并非完全微不足道,因此我在此处发布了固定代码。

如果有人理解我为什么必须在类型中添加const限定符,请告诉我。Fbinary_cont

#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;
}
于 2015-04-17T08:10:42.977 回答