Как уменьшить избыточный код при добавлении нового C++ 0x rvalue перегрузки ссылочного оператора

Я добавляю новые перегрузки оператора для использования в своих интересах C++ 0x rvalue ссылки, и я чувствую, что произвожу много избыточного кода.

У меня есть класс, 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"

Для каждой бинарной операции (как плюс), каждая сторона может быть любой lvalue tree, rvalue 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: класс на самом деле более сложен, чем просто дерево удваивается. Сокращение копий действительно существенно улучшает производительность моего проекта. Так, rvalue перегрузки стоят для меня, даже с дополнительным кодом. У меня есть подозрение, что мог бы быть способ обработать по шаблону далеко "бросок и передать" случаи выше, но я, может казаться, ни о чем не думаю.

19
задан Inverse 23 April 2010 в 04:13
поделиться

4 ответа

Просто быстрый поздний ответ: если рассматриваемый класс является перемещаемым, перемещение будет очень дешевым, и вы всегда будете перемещаться от всех аргументов, если можете, тогда передача аргументов по значению может быть вариантом:

tree operator +(tree      a, tree      b);

Если дерево можно перемещать и в качестве фактического аргумента передается rvalue ref, тогда аргументы функции будут инициализированы конструктором перемещения дерева, где это возможно, иначе конструктором копирования. Затем функция может делать со своими аргументами все, что угодно (например, перемещать свои внутренние элементы).

При передаче аргумента ссылки rvalue требуется дополнительный ход по сравнению с версией с большим количеством перегрузок, но я думаю, что в целом это лучше.

Кроме того, аргументы IMO, tree && могут принимать lvalue через временную копию, но это не то, что в настоящее время делают какие-либо компиляторы, поэтому это не очень полезно.

7
ответ дан 30 November 2019 в 05:06
поделиться

Во-первых, я не понимаю, почему оператор + вообще изменяет аргументы (разве это типичная реализация неизменяемого двоичного дерева), поэтому не будет никакой разницы между ссылкой на r-значение и l-значение. Но давайте предположим, что поддеревья имеют указатель на родителя или что-то в этом роде.

Из показанного вами примера использования похоже, что существует неявное преобразование из double в tree. В этом случае ваши случаи "cast and forward" не нужны, компилятор найдет определенное пользователем преобразование.

Разве перегрузки без перемещения не создают новый экземпляр для перехода в новое дерево? Если да, то я думаю, что вы можете написать три из оставшихся четырех дел в качестве экспедиторов.

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;
}
4
ответ дан 30 November 2019 в 05:06
поделиться

Я думаю, проблема в том, что вы определили операция с неконстантными параметрами. Если вы определяете

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

Нет разницы между r-значением и ссылкой на l-значение, поэтому вам не нужно определять также

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

Если вдобавок double конвертируется в дерево как tree x = 1.23; , давайте думать, вам не нужно ни define

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

, ни компилятор сделает всю работу за вас.

Вам нужно будет различать rvalues ​​и lvalues, если оператор + принимает параметр дерева по значению

tree operator +(tree a, tree b);
1
ответ дан 30 November 2019 в 05:06
поделиться

Вы должны определить их как функции-члены, чтобы вам не приходилось перегружать lvalue или rvalue в качестве основной единицы (что в любом случае не нужно).

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

, поскольку значение l или r первого не имеет значения. Кроме того, компилятор автоматически построит для вас, если этот конструктор доступен. Если дерево строится из double, вы можете автоматически использовать здесь double, и double будет соответствующим образом rvalue. Это всего лишь два метода.

3
ответ дан 30 November 2019 в 05:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: