gpt4 book ai didi

c++ - 使用对临时对象的引用的表达式模板可以重复使用吗?

转载 作者:行者123 更新时间:2023-12-03 07:22:24 26 4
gpt4 key购买 nike

我正在尝试围绕表达式模板进行思考。在 wikipedia article ,给出一个例子,其中表达式模板VecSum存储对其两个操作数的 const 引用。一个 Vec是一个包含 std::vector<double> 的表达式模板.我将首先提出我的问题,然后对下面的示例进行完整的概述。
我可以重用使用 const 对临时对象的引用的表达式吗?如果没有,我将如何实现轻量级、可重复使用的表达式模板?
三个Vec小号 a , b , 和 c表达式 a+b+c是类型

VecSum<VecSum<Vec, Vec>, Vec>
如果我理解正确,内部 VecSum是临时的和外部的 VecSum存储对内部 VecSum 的 const 引用.我相信内心的一生 VecSum临时保证直到表达式 a+b+c被评估。正确的?这是否意味着在没有创建悬空引用的危险的情况下不能重用表达式?
auto expr = a + b + c;
Vec v1 = expr; // ok
Vec v2 = expr; // not ok!
如果是这样,如何修改此示例,以便
  • 表达式可重用
  • 表达式不存储其操作数的拷贝(至少在不需要的情况下)?

  • 完整代码示例
    为了完整起见 - 如果维基百科文章同时更新,让我在这里重复示例代码并在 main 中给出示例我相信这会产生一个悬而未决的引用。
    #include <cassert>
    #include <vector>

    template <typename E>
    class VecExpression {
    public:
    double operator[](size_t i) const
    {
    // Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
    return static_cast<E const&>(*this)[i];
    }
    size_t size() const { return static_cast<E const&>(*this).size(); }
    };

    class Vec : public VecExpression<Vec> {
    std::vector<double> elems;

    public:
    double operator[](size_t i) const { return elems[i]; }
    double &operator[](size_t i) { return elems[i]; }
    size_t size() const { return elems.size(); }

    Vec(size_t n) : elems(n) {}

    // construct vector using initializer list
    Vec(std::initializer_list<double> init) : elems(init) {}

    // A Vec can be constructed from any VecExpression, forcing its evaluation.
    template <typename E>
    Vec(VecExpression<E> const& expr) : elems(expr.size()) {
    for (size_t i = 0; i != expr.size(); ++i) {
    elems[i] = expr[i];
    }
    }
    };

    template <typename E1, typename E2>
    class VecSum : public VecExpression<VecSum<E1, E2> > {

    E1 const& _u;
    E2 const& _v;

    public:

    VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
    assert(u.size() == v.size());
    }

    double operator[](size_t i) const { return _u[i] + _v[i]; }
    size_t size() const { return _v.size(); }
    };



    template <typename E1, typename E2>
    VecSum<E1, E2>
    operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) {
    return VecSum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
    }

    int main() {

    Vec v0 = {23.4,12.5,144.56,90.56};
    Vec v1 = {67.12,34.8,90.34,89.30};
    Vec v2 = {34.90,111.9,45.12,90.5};

    auto expr = v0 + v1 + v2;
    Vec v1 = expr; // ok
    Vec v2 = expr; // not ok!
    }


    编辑:
    我刚刚意识到这可能是 this question 的拷贝.然而,这两个问题的答案非常不同,而且都很有用。

    最佳答案

    上面的评论有一个非常有效的方法来检查悬空引用的问题。请注意,如果您尝试从示例中的 main 函数打印值,则程序仍然可以工作,因为绑定(bind)了悬空引用的对象也将在 main 的堆栈空间中创建。我试图将分配给 expr 的代码移动到函数内,但程序按预期崩溃(临时对象将在另一个堆栈帧中):

    auto makeExpr1(Vec const& v0, Vec const& v1, Vec const& v2) {
    return v0 + v1 + v2;
    }
    // ... in main:
    auto expr = makeExpr1(v0, v1, v2);
    您在此处突出显示的问题出现在创建可以在 C++ 等语言中延迟评估的表达式的情况下。在范围表达式(C++20 范围)的上下文中可能会出现某种类似的情况。
    下面是我修复该代码并使其与使用运算符 + 添加的左值和右值一起工作的快速尝试(对于丑陋的部分和可能的错误,我深表歉意)。这将仅在它们超出范围时存储它们的操作数的拷贝,并且将导致旧代码中的悬空引用。
    关于可重用性:只要您为每个操作定义一个类型和一个相应的运算符'?函数('?' 是操作的符号)这个方法应该为您提供对此类 vector 的任何二元操作的起点。
    #include <cassert>
    #include <vector>
    #include <utility>
    #include <iostream>

    /*
    * Passes lvalues and stores rvalues
    */
    template <typename T> class Wrapper;

    template <typename T> class Wrapper<T&> {
    private:
    T& ref;

    public:
    Wrapper(T& ref) : ref(ref) {}
    T& get() { return ref; }
    const T& get() const { return ref; }
    };

    template <typename T> class Wrapper<T&&> {
    private:
    T value;

    public:
    Wrapper(T&& ref) : value(std::move(ref)) {}
    T& get() { return value; }
    const T& get() const { return value; }
    };


    template <typename E>
    class VecExpression {
    public:
    double operator[](size_t i) const
    {
    // Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
    return static_cast<E const&>(*this)[i];
    }
    size_t size() const { return static_cast<E const&>(*this).size(); }
    };


    /*
    * Forwards the reference and const qualifiers
    * of the expression type to the expression itself
    */
    template <typename E> constexpr E& forwardRef(VecExpression<E>& ve) {
    return static_cast<E&>(ve);
    }

    template <typename E> constexpr const E& forwardRef(const VecExpression<E>& ve) {
    return static_cast<const E&>(ve);
    }

    template <typename E> constexpr E&& forwardRef(VecExpression<E>&& ve) {
    return static_cast<E&&>(ve);
    }


    class Vec : public VecExpression<Vec> {
    std::vector<double> elems;

    public:
    double operator[](size_t i) const { return elems[i]; }
    double &operator[](size_t i) { return elems[i]; }
    size_t size() const { return elems.size(); }

    Vec(size_t n) : elems(n) {}

    // construct vector using initializer list
    Vec(std::initializer_list<double> init) : elems(init) {}

    // A Vec can be constructed from any VecExpression, forcing its evaluation.
    template <typename E>
    Vec(VecExpression<E> const& expr) : elems(expr.size()) {
    std::cout << "Expr ctor\n"; // Very quick test
    for (size_t i = 0; i != expr.size(); ++i) {
    elems[i] = expr[i];
    }
    }

    // Move ctor added for checking
    Vec(Vec&& vec) : elems(std::move(vec.elems)) {
    std::cout << "Move ctor\n"; // Very quick test
    }
    };


    /*
    * Now VecSum is a sum between possibly const - qualified
    * and referenced expression types
    */
    template <typename E1, typename E2>
    class VecSum : public VecExpression<VecSum<E1, E2>> {

    Wrapper<E1> _u;
    Wrapper<E2> _v;

    public:

    VecSum(E1 u, E2 v) : _u(static_cast<E1>(u)), _v(static_cast<E2>(v)) {
    assert(_u.get().size() == _v.get().size());
    }

    double operator[](size_t i) const { return _u.get()[i] + _v.get()[i]; }
    size_t size() const { return _v.get().size(); }
    };

    /*
    * Used to create a VecSum by capturing also the reference kind
    * of the arguments (will be used by the Wrapper inside VecSum)
    */
    template <typename E1, typename E2>
    auto makeVecSum(E1&& e1, E2&& e2) {
    return VecSum<E1&&, E2&&>(std::forward<E1>(e1), std::forward<E2>(e2));
    }


    /*
    * Now the operator+ takes the vector expressions by universal references
    */
    template <typename VE1, typename VE2>
    auto operator+(VE1&& ve1, VE2&& ve2) {
    return makeVecSum(forwardRef(std::forward<VE1>(ve1)), forwardRef(std::forward<VE2>(ve2)));
    }


    // Now this will work
    auto makeExpr1(Vec const& v0, Vec const& v1, Vec const& v2) {
    return v0 + v1 + v2;
    }

    // This will also work - the rvalue is stored in the
    // expression itself and both will have the same lifetime
    auto makeExpr2(Vec const& v0, Vec const& v1) {
    return v0 + v1 + Vec({1.0, 1.0, 1.0, 1.0});
    }

    int main() {

    Vec v0 = {23.4,12.5,144.56,90.56};
    Vec v1 = {67.12,34.8,90.34,89.30};
    Vec v2 = {34.90,111.9,45.12,90.5};

    auto expr = makeExpr1(v0, v1, v2);
    Vec v1_ = expr;
    Vec v2_ = expr;
    auto expr_ = makeExpr2(v0, v1);

    for (size_t i = 0; i < v1_.size(); ++i)
    std::cout << v1_[i] << " ";
    std::cout << std::endl;

    for (size_t i = 0; i < v2_.size(); ++i)
    std::cout << v2_[i] << " ";
    std::cout << std::endl;

    for (size_t i = 0; i < expr.size(); ++i)
    std::cout << expr[i] << " ";
    std::cout << std::endl;

    for (size_t i = 0; i < expr_.size(); ++i)
    std::cout << expr_[i] << " ";
    std::cout << std::endl;
    }

    关于c++ - 使用对临时对象的引用的表达式模板可以重复使用吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64750197/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com