C ++中的高效向量运算符/对临时对象的引用

时间:2015-07-06 07:32:11

标签: c++ reference polymorphism

我正在尝试编写一个存储数据数组的C ++向量类,并允许逐个元素地执行数学运算。我想以这样一种方式实现这一点,即表达式a = b + c + d应该只遍历所有元素一次并直接将总和b[i] + c[i] + d[i]写入a[i]而不创建中间向量。

我写的是这样的:

template<class T, int N>
class VectorExpression {
  public:
    virtual T operator[] (int i) const = 0;

    virtual ~VectorExpression() {}
}

template<class T, int N>
class MyVector : public VectorExpression<T, N> {
    T data[N];

  public:
    T& operator[] (int i) { return data[i]; }
    T& const operator[] (int i) const { return data[i]; }

    MyVector<T,N>& operator=(const VectorExpression<T,N> &rhs) {
      for (int i = 0; i < N; ++i)
        data[i] = rhs[i];

      return *this;
    }
}

template<class T, int N>
class VectorSum : public VectorExpression<T, N> {
    VectorExpression<T,N> &a, &b;

  public:
    VectorSum(VectorExpression<T,N> &aa, VectorExpression<T,N> &bb)
    : a(aa), b(bb) {}

    T operator[] (int i) const { return a[i] + b[i]; }
}

template<class T, int N>
VectorSum<T,N> operator+(const VectorExpression<T,N> &a, 
        const VectorExpression<T,N> &b) 
{
  return VectorSum<T,N>(a, b);
}

int main() {
  MyVector<double,10> a, b, c, d;

  // Initialize b, c, d here

  a = b + c + d;

  return 0;
}

可能这个功能是由valarray类提供的,但是因为我试图将它剥离到一个最小的例子。

我将operator[]设为虚拟,因为这允许嵌套所有类型的表达式(例如a = !(-b*c + d)),前提是我将定义所有运算符和类似于VectorSum的相应类。

我使用引用,因为普通变量不是多态的,指针不适用于运算符重载。

现在我对此的疑问是:

  • 在语句a = b + c + d;中,将创建两个临时VectorSum<double,10>对象,分别用于存储b + c(b+c) + d。他们会活得足够长,以使多态行为有效吗?更具体地说,(b+c) + d会存储对b + c的引用,但在调用operator=时该对象是否仍然存在?根据{{​​3}},所有临时值应该存在,直到operator=返回,但这对于旧版本的C ++也适用吗?

  • 如果没有,那怎么办?我看到的唯一选择是使用VectorSum分配new个对象,通过引用返回它们然后在operator=函数中删除它们,但这看起来有点麻烦,可能是效率低下。我也不确定它是否总是安全的。

  • (小问题)可以在T中覆盖VectorExpression::operator[]的{​​{1}}的返回类型T& const吗?

修改

我在operator +中有错误的参数类型:将它们从MyVector更改为VectorSum

2 个答案:

答案 0 :(得分:0)

在这里,我想出了什么:

#include <iostream>
#include <initializer_list>
#include <algorithm>


template<class T, int N>
class VectorExpression {
public:
    virtual T operator[] (int i) = 0;
    virtual const T operator[] (int i) const = 0;

    virtual ~VectorExpression() {}
};


template<class T, int N>
class MyVector : public VectorExpression<T, N> {
  T data[N];

public:
  MyVector() {
    // initialize zero
    std::fill(std::begin(data), std::end(data), T());
  }

  MyVector(const std::initializer_list<T>& values) {
    // initialize from array initializer_list
    std::copy(std::begin(values), std::end(values), data);
  }

  MyVector(const VectorExpression<T,N>& rhs) { 
    for (int i = 0; i < N; ++i)
      data[i] = rhs[i];
  }

  MyVector<T,N>& operator=(const VectorExpression<T,N>& rhs) {
    for (int i = 0; i < N; ++i)
      data[i] = rhs[i];

    return *this;
  }

  T operator[] (int i) { return data[i]; }
  const T operator[] (int i) const { return data[i]; }

  friend std::ostream& operator<<(std::ostream& stream, MyVector& obj) { 
    stream << "[";
    for (int i = 0; i < N; ++i) { 
      stream << obj.data[i] << ", ";
    }
    stream << "]";

    return stream;
  }
};

template<class T, int N>
class VectorSum : public VectorExpression<T, N> {
  const MyVector<T,N> &a, &b;

public:
  VectorSum(const MyVector<T,N>& aa, const MyVector<T,N>& bb):
    a(aa), b(bb) {
  }

  T operator[] (int i) { return return a[i] + b[i]; }

  const T operator[] (int i) const { return a[i] + b[i]; }
};


template<class T, int N>
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) {
  return VectorSum<T,N>(a, b);
}


int main() {
  MyVector<double,3> a, b({1,2,3}), c({3,4,5}), d({4,5,6});

  a = b + c + d; 

  std::cout << b << std::endl;
  std::cout << c << std::endl;
  std::cout << d << std::endl;
  std::cout << "Result:\n" << a << std::endl;

  return 0;
}

输出:

[1, 2, 3, ]
[3, 4, 5, ]
[4, 5, 6, ]
Result:
[8, 11, 14, ]

我已经添加了一个initializer_list(C ++ 11)构造函数和ostream运算符,纯粹是出于方便/说明的目的。

由于您已将operator []定义为按值返回,因此我无法在数据数组中设置项目以进行测试(因为错误:左值作为赋值的左操作数需要);通常这个运算符应该是引用 - 但是,在你的情况下,VectorSum :: operator []将不起作用,因为由于返回对临时的引用而导致编译失败。

我还添加了一个复制构造函数,因为......

// this calls MyVector's copy constructor when assigned to 'main::a'
template<class T, int N>
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) {
  return VectorSum<T,N>(a, b);   // implicit MyVector::copy constructor 
}

// this also calls MyVector's copy constructor (unless the copy constructor is defined explicit)
template<class T, int N>
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) {
  MyVector<T,N> res = VectorSum<T,N>(a, b);
  return res;
}

// but this would call MyVector's assignment operator
template<class T, int N>
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) {
  MyVector<T,N> res;
  res = VectorSum<T,N>(a, b);
  return res;
}

回答你的问题:

  1. 是 - 如果您明确定义了变量,它会如何表现? 回来了吗?除了那里,临时工的行为也是一样的 不是变量声明;
  2. 不适用
  3. 我触及了上面这个 - 你不能 使用引用是因为&#39;返回对临时&#39;的引用错误; 但是没有理由你可以添加T&amp; operator []到MyVector(即不覆盖)。
  4. 编辑:评论的答案:

    1. 当覆盖包括返回类型时,函数规范必须相同。由于您已在VectorExpression中按值返回它,因此必须在MyVector中按值返回。如果您尝试将其更改为子类中的引用,则会出现编译错误:指定了冲突的返回类型。所以,你不能用一个返回const T&amp;的版本来覆盖operator const。而且,必须按值返回,因为MyVectorSum返回{ a[i] + b[i] },这将是一个临时的,您无法返回对临时的引用。

    2. 抱歉我的错误,修正如上。

    3. ,因为:

      • MyVector不是VectorSum的子类型 - 编译错误'MyVector'不是从'const VectorSum'派生的
      • 我也尝试使用VectorExpression但编译错误:&#39;无法分配抽象类型的对象&#39; - 因为它试图按价值返回
      • 我选择了MyVector,因为那是您预期结果的类型。是的,所有那些for循环,但我不能看到一个方法:有三个不同的数组&#39;数据&#39;变量中的每一个都需要迭代才能被累积。在代码的某些时候,你将不得不做for循环。
    4. 明白了,是的,我感到很困惑。从帖子中删除。

答案 1 :(得分:0)

我一开始并没有想到这一点,但是使用虚拟operator[]方法可能会通过避免3个for-loops和矢量大小的临时中间存储来达到我试图实现的效率。使方法虚拟化可以防止它被内联,这意味着每次访问元素时都需要将其实际调用为函数

根据我从评论到我的问题和谷歌的人那里得到的链接,我最终得到了以下解决方案,避免了需要任何虚拟方法。

template<class T, int N, class V>
class VectorExpressionBase {
    V ref;

protected:
    explicit VectorExpressionBase(const V *ref) 
    : ref(const_cast<V*>(ref)) {}

public:
    T  operator[] (int i) const { return ref[i]; }
    T& operator[] (int i)       { return ref[i]; }
}

template<class T, int N>
class VectorExpressionBase<T,N,void> {
    T data[N];

protected:
    explicit VectorExpressionBase(const void*) {
        // Argument is unused but accepted to have uniform
        // calling syntax
    }

public:
    T  operator[] (int i) const { return data[i]; }
    T& operator[] (int i)       { return data[i]; }
}

template<class T, int N, class V>
class VectorExpression : public VectorExpressionBase<T,N,V> {
public:
    template<class V1>
    VectorExpression<T,N,V>& operator= (
        const VectorExpression<T,N,V1> &rhs) 
    {
        for (int i = 0; i < N; ++i)
            data[i] = rhs[i];

        return *this;
    }

    explicit VectorExpression(const V *ref = 0) 
    : VectorExpressionBase<T,N,V>(ref) {}

    // Can define all kinds of operators and functions here such as
    // +=, *=, unary + and -, max(), min(), sin(), cos(), ...
    // They would automatically apply to MyVector objects and to the
    // results of other operators and functions
};

template<class T, int N>
class MyVector : public VectorExpression<T,N,void> {
}

template<class T, int N, class VA, class VB>
class VectorSum {
    VectorExpression<T,N,VA> &a;
    VectorExpression<T,N,VB> &b;

  public:
    VectorSum(VectorExpression<T,N,VA> &aa, VectorExpression<T,N,VB> &bb)
    : a(aa), b(bb) {}

    T operator[] (int i) const { return a[i] + b[i]; }
}

template<class T, int N, class VA, class VB>
VectorExpr<T,N,VectorSum<T,N,VA,VB> >
operator+(const VectorExpression<T,N,VA> &a, 
          const VectorExpression<T,N,VB> &b) 
{
    VectorSum<T,N,VA,VB> sum(a, b);
    return VectorExpr<T,N,VectorSum<T,N,VA,VB> >(sum);
}

VectorExpression现在只包装完成工作的类(在本例中为VectorSum。这允许仅为VectorExpression定义所有类型的函数和运算符,而不是必须重载它们适用于VectorSumVectorProduct

MyVector派生自VectorExpression的特殊情况,它具有专门的基类;这不是必需的,但它很好,因为它使VectorExpression定义的所有函数和运算符也可用于MyVector。通过使用仅处理存储和VectorExpressionBase运算符的简单基类[],所有其他运算符和方法不需要在V = void的专门化中重复。

用户只需要了解类MyVector<T,N>(用于存储数据),如果想要定义其他函数和运算符,可能需要了解VectorExpression<T,N,V>VectorExpressionBaseVectorSum等类不需要对外界可见。

我发现我的原始解决方案在概念上更清晰,因为每个类的含义更清晰,因为它不需要模板参数V,但是这个更有效,因为它不需要任何虚拟函数,在某些情况下可能会产生很大的差异。

感谢您指点我正确的链接!

P.S。当然,大多数/所有这些都不是新的,但我认为总结和解释一下会很好。我希望它可以帮助别人。

修改

我将数据成员VectorExpressionBase<T,N,V>::ref的类型从V&更改为V。这是必需的,因为在评估V时,引用指向的临时VectorExpression对象可能不再存在。例如,临时VectorSum对象在operator+函数返回时停止存在,使返回的VectorExpression对象无效。

我还使用一些构造函数完成了代码并更正了operator+函数。