我正在尝试编写一个存储数据数组的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
。
答案 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;
}
回答你的问题:
编辑:评论的答案:
当覆盖包括返回类型时,函数规范必须相同。由于您已在VectorExpression中按值返回它,因此必须在MyVector中按值返回。如果您尝试将其更改为子类中的引用,则会出现编译错误:指定了冲突的返回类型。所以,你不能用一个返回const T&amp;的版本来覆盖operator const。而且,必须按值返回,因为MyVectorSum返回{ a[i] + b[i] }
,这将是一个临时的,您无法返回对临时的引用。
抱歉我的错误,修正如上。
,因为:
明白了,是的,我感到很困惑。从帖子中删除。
答案 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
定义所有类型的函数和运算符,而不是必须重载它们适用于VectorSum
,VectorProduct
等
MyVector
派生自VectorExpression
的特殊情况,它具有专门的基类;这不是必需的,但它很好,因为它使VectorExpression
定义的所有函数和运算符也可用于MyVector
。通过使用仅处理存储和VectorExpressionBase
运算符的简单基类[]
,所有其他运算符和方法不需要在V = void
的专门化中重复。
用户只需要了解类MyVector<T,N>
(用于存储数据),如果想要定义其他函数和运算符,可能需要了解VectorExpression<T,N,V>
。 VectorExpressionBase
和VectorSum
等类不需要对外界可见。
我发现我的原始解决方案在概念上更清晰,因为每个类的含义更清晰,因为它不需要模板参数V
,但是这个更有效,因为它不需要任何虚拟函数,在某些情况下可能会产生很大的差异。
感谢您指点我正确的链接!
P.S。当然,大多数/所有这些都不是新的,但我认为总结和解释一下会很好。我希望它可以帮助别人。
修改强>
我将数据成员VectorExpressionBase<T,N,V>::ref
的类型从V&
更改为V
。这是必需的,因为在评估V
时,引用指向的临时VectorExpression
对象可能不再存在。例如,临时VectorSum
对象在operator+
函数返回时停止存在,使返回的VectorExpression
对象无效。
我还使用一些构造函数完成了代码并更正了operator+
函数。