19

我正在添加新的运算符重载以利用 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、右值treedouble. 这导致每个二元操作有 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:这个类实际上比一个双打树更复杂。减少副本确实显着提高了我的项目的性能。所以,右值重载对我来说是值得的,即使有额外的代码。我怀疑可能有一种方法可以将上面的“cast and forward”案例模板化,但我似乎想不出任何东西。

4

4 回答 4

7

只是一个迟到的答案:如果有问题的类是可移动的,那么移动非常便宜,如果可以的话,你总是会从所有参数中移动,那么按值传递参数可能是一种选择:

tree operator +(tree      a, tree      b);

如果树是可移动的并且一个右值引用作为实际参数传递,那么函数的参数将尽可能使用树的移动构造函数初始化,否则使用复制构造函数。然后,该函数可以用它的参数以适当的方式做任何事情(比如,移动它们的内部)。

与很多重载版本相比,在传递右值引用参数时确实会产生额外的移动,但我认为它通常更好。

此外,IMO,tree &&参数可能应该通过临时副本接受左值,但这不是任何编译器当前所做的,所以它不是很有用。

于 2010-05-01T08:17:13.780 回答
4

首先,我不明白为什么 operator+ 会修改参数(这不是典型的不可变二叉树实现),所以 r-value 和 l-value 引用之间没有区别。但是让我们假设子树有一个指向父级的指针或类似的东西。

从您展示的使用示例中,看起来存在从双精度到树的隐式转换。在这种情况下,不需要您的“强制转换和转发”情况,编译器会找到用户定义的转换。

非移动重载最终不会使新实例进入新树吗?如果是这样,我认为您可以将剩下的四个案例中的三个写成货运代理。

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); }

当然,您可以使用宏来帮助生成每个算子的三个(或七个)转发版本。

编辑:如果这些调用不明确或解决递归,如何:

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)); }

编辑:操作员未能使用隐式转换的重现:

#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-23T04:26:51.620 回答
3

您应该将它们定义为成员函数,这样您就不必重载左值或右值作为主要单元(无论如何这都是不必要的)也就是说,

class Tree {
    Tree operator+ const (const Tree&);
    Tree operator+ const (Tree&&);
};

因为第一个的 l 或 r 值无关紧要。此外,如果该构造函数可用,编译器将自动为您构造。如果树是从 double 构造的,那么您可以在此处自动使用 double,并且 double 将适当地成为右值。这只是两种方法。

于 2010-04-26T18:08:43.627 回答
1

我认为问题在于您已经使用非 const 参数定义了操作。如果你定义

tree operator +(const tree& a, const tree& b);

r-value 和 l-value reference 没有区别,所以你也不需要定义

tree operator +(tree&&      a, const tree& b);

如果另外 double 可以转换为 treetree x = 1.23;让我们想,你不需要定义

tree operator +(double      a, const tree& b){ return tree(a) + b; }

编译器将为您完成工作。

如果 operator+ 按值获取树参数,则需要区分右值和左值

tree operator +(tree a, tree b);
于 2010-04-23T17:07:47.053 回答