c++ - C++ 中的高效 vector 运算符/对临时对象的引用

标签 c++ reference polymorphism

我正在尝试编写一个 C++ vector 类,它存储数据数组并允许在逐个元素的基础上执行数学运算。我想以表达式 a = b + c + d 的方式实现它应该只遍历所有元素一次并直接写入总和 b[i] + c[i] + d[i]a[i]无需创建中间 vector 。

我在写这样的东西:

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[] virtual 因为这允许嵌套所有类型的表达式(例如 a = !(-b*c + d) ),前提是我将定义所有运算符和类似于 VectorSum 的相应类.

我使用引用是因为普通变量不是多态的,而且指针不能与运算符重载一起使用。

现在我的问题是:

  • 在声明中a = b + c + d; , 两个临时 VectorSum<double,10>将创建对象来存储 b + c(b+c) + d分别。它们的生命周期是否足以使多态行为起作用?更具体地说,(b+c) + d将存储对 b + c 的引用, 但当 operator= 时该对象是否仍然存在叫做?根据this post所有临时对象都应该存在直到 operator=返回,但这是否也适用于旧版本的 C++?

  • 如果不是,那么这是怎么做到的?我看到的唯一选择是分配 VectorSum使用 new 的对象, 通过引用返回它们,然后在 operator= 中删除它们功能,但这似乎有点麻烦,而且效率可能低得多。我也不确定它是否总是安全的。

  • (小问题)是否可以覆盖返回类型 TVectorExpression::operator[]通过 T& constMyVector ?

编辑

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

最佳答案

这就是我想出的:

#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. 我在上面提到过这个 - 你不能 由于“返回对临时的引用”错误而使用引用; 但是,没有理由可以将 T& operator[] 添加到 MyVector(即不覆盖)。

编辑:对评论的回答:

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

  2. 对不起,我的错误,已在上面修复。

  3. 因为:

    • MyVector 不是 VectorSum 的子类型 - 编译错误“MyVector”不是从“const VectorSum”派生的
    • 我也尝试过使用 VectorExpression 但编译错误:“无法分配抽象类型的对象”- 因为它试图按值返回
    • 我选择了 MyVector,因为这是您预期结果的类型。是的,它执行了所有那些 for 循环,但我看不出解决方法:有三个不同的数组“数据”变量,每个变量都需要迭代才能累积。在代码中的某个位置,您将不得不执行 for 循环。
  4. 明白了,是的,我很困惑。从帖子中删除。

关于c++ - C++ 中的高效 vector 运算符/对临时对象的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31240072/

相关文章:

C++ 字符指针

Java:父方法访问子类的静态变量?

自定义 "non-traditional"多态性实现

c++ - 适用于 Windows 上 Clang 的 CMake 生成器

c++ - 使用 AX_BOOST_BASE 进行 autoconf boost 检测

python - 如何克隆列表以使其在分配后不会意外更改?

C++ - 在堆上分配而不是使用新对象作为引用来维护接口(interface)

c++ - vsync "wait"(阻塞)在什么时候发生?

c++ - std::vector 的 std::functions 查找

c++ - 用于访问多态类型的 std::visit-like 函数