我正在添加新的操作符重载来利用c++0x右值引用,并且我感觉我正在生成许多冗余代码。
我有一个名为tree
的类,它包含一个关于双精度值的代数运算树。下面是一个示例用例:
tree x = 1.23;
tree y = 8.19;
tree z = (x + y)/67.31 - 3.15*y;
...
std::cout << z; // prints "(1.23 + 8.19)/67.31 - 3.15*8.19"
对于每个二元运算(如加号),每个边可以是左值、右值tree
或double
。这会导致每个二进制操作产生8个重载:
// core rvalue overloads for plus:
tree operator +(const tree& a, const tree& b);
tree operator +(const tree& a, tree&& b);
tree operator +(tree&& a, const tree& b);
tree operator +(tree&& a, tree&& b);
// cast and forward cases:
tree operator +(const tree& a, double b) { return a + tree(b); }
tree operator +(double a, const tree& b) { return tree(a) + b; }
tree operator +(tree&& a, double b) { return std::move(a) + tree(b); }
tree operator +(double a, tree&& b) { return tree(a) + std::move(b); }
// 8 more overloads for minus
// 8 more overloads for multiply
// 8 more overloads for divide
// etc
对于每个二元运算(减法、乘法、除法等)也必须以某种方式重复。
正如您所看到的,实际上我只需要编写4个函数;其他4个函数可以转换并转发到核心用例。
您对减小此代码的大小有什么建议吗?
PS:这个类实际上比doubles树要复杂得多。减少副本确实可以极大地提高项目的性能。所以,rvalue重载对我来说是值得的,即使有额外的代码。我怀疑可能会有一种方法来消除上面的“抛出和转发”案例,但我似乎想不出任何东西。
发布于 2010-05-01 16:17:14
简单地说一下:如果所讨论的类是可移动的,那么移动成本非常低,并且如果可以,您总是可以从所有参数中移动,然后按值传递参数可能是一种选择:
tree operator +(tree a, tree b);
如果tree是可移动的,并且右值ref被作为实际参数传递,则函数的参数将在可能的情况下使用tree的移动构造函数初始化,否则使用复制构造函数初始化。然后,函数可以以适当的方式对其参数做任何它想做的事情(例如,移动它们的内部)。
与大量重载的版本相比,当传递一个右值引用参数时,它确实会引起额外的移动,但我认为它通常更好。
此外,IMO,tree &&
参数可能会通过临时副本接受左值,但这不是任何编译器目前所做的,所以它不是很有用。
发布于 2010-04-23 12:26:52
首先,我根本不明白为什么operator+要修改参数(这不是一个典型的不可变的二叉树实现吗),所以在r值和l值引用之间没有区别。但是让我们假设子树有一个指向父树的指针或类似的东西。
从您展示的用法示例中,看起来有一个从双精度到树的隐式转换。在这种情况下,您的“转换和转发”情况是不需要的,编译器将找到用户定义的转换。
非移动重载不会最终生成一个新实例进入新树吗?如果是这样的话,我想你可以把剩下的四个案例中的三个作为货代来写。
tree operator +(tree&& a, tree&& b); // core case
tree operator +(tree a, tree b) { return std::move(a) + std::move(b); }
tree operator +(tree a, tree&& b) { return std::move(a) + std::move(b); }
tree operator +(tree&& a, tree b) { return std::move(a) + std::move(b); }
当然,您可以使用宏来帮助生成每个操作符的三个(或七个)转发版本。
EDIT:如果这些调用是不明确的,或者解析为递归,那么:
tree add_core(tree&& a, tree&& b);
tree operator +(tree&& a, tree&& b) { return add_core(std::move(a), std::move(b)); }
tree operator +(tree a, tree b) { return add_core(std::move(a), std::move(b)); }
tree operator +(tree a, tree&& b) { return add_core(std::move(a), std::move(b)); }
tree operator +(tree&& a, tree b) { return add_core(std::move(a), std::move(b)); }
EDIT:运算符使用隐式转换失败的再现:
#include <iostream>
template<typename T>
class tree;
template<typename T> tree<T> add(tree<T> a, tree<T> b)
{
std::cout << "added!" << std::endl << std::endl;
return tree<T>();
}
template<typename T> tree<T> operator +(tree<T> a, tree<T> b) { return add(a, b); }
template<typename T>
class tree
{
public:
tree() { }
tree(const tree& t) { std::cout << "copy!" << std::endl; }
tree(double val) { std::cout << "double" << std::endl; }
friend tree operator +<T>(tree a, tree b);
};
int main()
{
tree<double>(1.0) + 2.0;
return 0;
}
和不带模板的版本,其中隐式转换有效:
#include <iostream>
class tree
{
public:
tree() { }
tree(const tree& t) { std::cout << "copy!" << std::endl; }
tree(double val) { std::cout << "double" << std::endl; }
friend tree operator +(tree a, tree b);
};
tree add(tree a, tree b)
{
std::cout << "added!" << std::endl << std::endl;
return tree();
}
tree operator +(tree a, tree b) { return add(a, b); }
int main()
{
tree(1.0) + 2.0;
return 0;
}
发布于 2010-04-27 02:08:44
你应该将它们定义为成员函数,这样你就不必重载左值或右值作为主要单元(无论如何这是不必要的),即,
class Tree {
Tree operator+ const (const Tree&);
Tree operator+ const (Tree&&);
};
因为第一个的l或r值是不相关的。此外,如果该构造函数可用,编译器将自动为您构造。如果树是从double构造的,那么您可以在这里自动使用double,double将是一个合适的右值。这只是两种方法。
https://stackoverflow.com/questions/2696156
复制相似问题