本文介绍了在没有不必要的副本的情况下处理vector_binary_operation类中对expressions的引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现一个简单的 vector 类,该类使用,以避免诸如 vector w = x + y + z 这样的表达式效率低下地实现(该实现会首先产生一个临时容纳 x + y 的向量,然后产生另一个 vector 和元素 z

I'm trying to implement a simple vector class which uses expression templates in order to avoid that expressions such as vector w = x + y + z are implemented inefficiently (the implementation would first produce a temporary vector to hold x + y and then produce another vector with the elements of z added in):

namespace math
{
    template<class E>
    class expression
    {
    public:
        auto size() const {
            return static_cast<E const&>(*this).size();
        }

        auto operator[](std::size_t i) const
        {
            if (i >= size())
                throw std::length_error("");
            return static_cast<E const&>(*this)[i];
        }

        operator E&() { return static_cast<E&>(*this); }
        operator E const&() const { return static_cast<E const&>(*this); }
    }; // class expression

    template<typename T, class Allocator = std::allocator<T>>
    class vector
        : public expression<vector<T>>
    {
    private:
        using data_type = std::vector<T, Allocator>;
        data_type m_data;

    public:
        using value_type = T;
        using allocator_type = Allocator;
        using size_type = typename data_type::size_type;
        using difference_type = typename data_type::difference_type;
        using reference = typename data_type::reference;
        using const_reference = typename data_type::const_reference;
        using pointer = typename data_type::pointer ;
        using const_pointer = typename data_type::const_pointer;

        vector(size_type d)
            : m_data(d)
        { }
        vector(std::initializer_list<value_type> init)
            : m_data(init)
        { }
        template<class E>
        vector(expression<E> const& expression)
            : m_data(expression.size())
        {
            for (size_type i = 0; i < expression.size(); ++i)
                m_data[i] = expression[i];
        }

        size_type size() const {
            return m_data.size();
        }
        
        value_type  operator[](size_type i) const { return m_data[i]; }
        value_type& operator[](size_type i)       { return m_data[i]; };
    }; // class vector

    namespace detail
    {
        template<typename T>
        class scalar
            : public expression<scalar<T>>
        {
        public:
            using value_type = T;
            using allocator_type = std::allocator<void>;
            using size_type = typename std::allocator<T>::size_type;
            using difference_type = typename std::allocator<T>::difference_type;
            using reference = typename std::allocator<T>::reference;
            using const_reference = typename std::allocator<T>::const_reference;
            using pointer = typename std::allocator<T>::pointer;
            using const_pointer = typename std::allocator<T>::const_pointer;

            scalar(value_type value)
                : m_value(value)
            { }

            size_type size() const {
                return 0;
            }

            operator value_type&() { return static_cast<value_type&>(*this); }
            operator value_type const&() const { return static_cast<value_type const&>(*this); }

            value_type  operator[](size_type i) const { return m_value; }
            value_type& operator[](size_type i) { return m_value; }

        private:
            value_type m_value;
        }; // class scalar

        template<class>
        struct is_scalar : std::false_type { };

        template<class T>
        struct is_scalar<scalar<T>> : std::true_type { };
    } // namespace detail

    template<class E1, class E2, class BinaryOperation>
    class vector_binary_operation
        : public expression<vector_binary_operation<E1, E2, BinaryOperation>>
    {
    public:
        using value_type = decltype(BinaryOperation()(typename E1::value_type(), typename E2::value_type()));
        using allocator_type = std::conditional_t<
            detail::is_scalar<E1>::value,
            typename E2::allocator_type::template rebind<value_type>::other,
            typename E1::allocator_type::template rebind<value_type>::other>;

    private:
        using vector_type = vector<value_type, allocator_type>;

    public:
        using size_type = typename vector_type::size_type;
        using difference_type = typename vector_type::difference_type;
        using reference = typename vector_type::reference;
        using const_reference = typename vector_type::const_reference;
        using pointer = typename vector_type::pointer;
        using const_pointer = typename vector_type::const_pointer;

        vector_binary_operation(expression<E1> const& e1, expression<E2> const& e2, BinaryOperation op)
            : m_e1(e1), m_e2(e2),
              m_op(op)
        {
            if (e1.size() > 0 && e2.size() > 0 && !(e1.size() == e2.size()))
                throw std::logic_error("");
        }

        size_type size() const {
            return m_e1.size(); // == m_e2.size()
        }

        value_type operator[](size_type i) const {
            return m_op(m_e1[i], m_e2[i]);
        }

    private:
        E1 m_e1;
        E2 m_e2;
        //E1 const& m_e1;
        //E2 const& m_e2;
        BinaryOperation m_op;
    }; // class vector_binary_operation

    template<class E1, class E2>
    vector_binary_operation<E1, E2, std::plus<>>
    operator+(expression<E1> const& e1, expression<E2> const& e2) {
        return{ e1, e2, std::plus<>() };
    }
    template<class E1, class E2>
    vector_binary_operation<E1, E2, std::minus<>>
    operator-(expression<E1> const& e1, expression<E2> const& e2) {
        return{ e1, e2, std::minus<>() };
    }
    template<class E1, class E2>
    vector_binary_operation<E1, E2, std::multiplies<>>
    operator*(expression<E1> const& e1, expression<E2> const& e2) {
        return{ e1, e2, std::multiplies<>() };
    }
    template<class E1, class E2>
    vector_binary_operation<E1, E2, std::divides<>>
    operator/(expression<E1> const& e1, expression<E2> const& e2) {
        return{ e1, e2, std::divides<>() };
    }

    template<class E, typename T>
    vector_binary_operation<E, detail::scalar<T>, std::divides<>>
    operator/(expression<E> const& expr, T val) {
        return{ expr, detail::scalar<T>(val), std::divides<>() };
    }
    template<class E, typename T>
    vector_binary_operation<E, detail::scalar<T>, std::multiplies<>>
    operator*(T val, expression<E> const& expr) {
        return{ expr, detail::scalar<T>(val), std::multiplies<>() };
    }
    template<class E, typename T>
    vector_binary_operation<E, detail::scalar<T>, std::multiplies<>>
    operator*(expression<E> const& expr, T val) {
        return{ expr, detail::scalar<T>(val), std::multiplies<>() };
    }
} // namespace math

我不知道该如何处理 vector_binary_operation 中的 expression 成员变量。将它们声明为const引用很有意义,因为代码的全部目的是避免不必要的复制。但是,如果我们写 sum = a + b + c ,我们最终将保留对临时 a + b 。进行 sum [0] 将在该临时目录上调用 operator()[0] 。但是该对象已在上一行之后删除。

I don't know how I need to deal with the expression member variables in vector_binary_operation. It would make sense to declare them as const references, cause the whole point of the code is to avoid unnecessary copies. However, if we write sum = a + b + c, we will end up keeping a reference to the temporary a + b. Doing sum[0] will call operator()[0] on that temporary. But that object was deleted after the previous line.

我该怎么办?

推荐答案

很早以前,我在表达式模板代码中也面临着类似的决定。因此,如何存储包装好的阵列的选择至关重要,应谨慎选择。但是,假设您已经满足于引用:也就是说,如果您拥有类似

I was facing a similar decision in my expression template code quite some time ago. The choice of how to store the wrapped arrays is thereby of central importance and should be made with care. But let's assume you already settled for references: that is, if you have code like

vector a;
vector b;
auto c = a + b;

a 和 b 通过const-reference存储。这意味着只要使用 c ,原始向量就必须保持活动状态。 (其他选择可能是按值存储,这可能会很昂贵,或者是按(包装的)共享指针存储,让您使用 c ,即使 a 或 b 离开了范围,但伴随着通话开销。)

a and b are stored by const-reference. This implies that as long as you use c, the original vectors have to be kept alive. (Other choices would be store-by-value, which can be expensive, or store-by-(wrapped)-shared-pointer, which let's you use c even if a or b left their scope but comes with a call overhead.)

好,但是现在当您准备使用引用时,您显然希望避免引用临时引用。为此,C ++移动语义已经提供了您需要的任何东西:

Ok, but now as you settled for references, you obviously want to avoid references to a temporary. For that, C++ move semantics offers already anything you need:

template<typename E1, typename E2>
auto operator+(E1&& e1, E2&& e2)
{
     using value_type = decltype(e1[0] + e2[0]);
     return vector_binary_operation<E1, E2, std::plus<value_type> >
                   {std::forward<E1>(e1), std::forward<E2>(e2), std::plus<value_type>{}};
}

与代码的区别在于所使用的的类型推导rvalue 引用 E1& 和 E2& 。它们的目的是指示函数是获得有效的命名对象还是临时对象。基于此信息,您可以在此函数中选择如何将传递的引用存储在返回的表达式类中。

The difference to your code is in the type deduction of the used rvalue references E1&& and E2&&. Their purpose is to indicate whether the function got a valid and named object or a temporary. Based on this information, in this function you can make the choice how to store the passed references in the returned expression classes.

例如,如果您现在添加两个向量

For example, if you now add two vectors

auto c = a + b;

E1 和 E2 都将推导出为 vector& 。相反,如果您拥有

E1 and E2 will be deduced both as vector&. On the opposite, if you have

auto d = (a + b) + b;

E1 是 vector_binary_operation< vector& ;, vector& ;, std :: plus< > -在这里没有引用-而E2仍然是 vector& 。

E1 is vector_binary_operation<vector&, vector&, std::plus<> > -- no reference here -- whereas E2 is still vector&.

这篇关于在没有不必要的副本的情况下处理vector_binary_operation类中对expressions的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 07:56