Как уменьшить избыточный код при добавлении новых перегруженных ссылочных операторов c ++ 0x rvalue - PullRequest
19 голосов
/ 23 апреля 2010

Я добавляю новые перегрузки операторов, чтобы воспользоваться преимуществами ссылок 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 имеют смысл для меня, даже с дополнительным кодом.У меня есть подозрение, что может существовать способ шаблона прочь вышеупомянутых дел "приведение и пересылка", но я не могу думать ни о чем.

Ответы [ 4 ]

7 голосов
/ 01 мая 2010

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

tree operator +(tree      a, tree      b);

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

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

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

4 голосов
/ 23 апреля 2010

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

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

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

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

РЕДАКТИРОВАТЬ: repro оператора не использовать неявные преобразования:

#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;
}
3 голосов
/ 26 апреля 2010

Вы должны определить их как функции-члены, чтобы вам не приходилось перегружать lvalue или rvalue как основной модуль (что в любом случае не нужно)значение l или r первого не имеет значения.Кроме того, компилятор будет автоматически создавать для вас, если этот конструктор доступен.Если дерево строится из double, тогда вы можете автоматически использовать double здесь, и double будет соответственно rvalue.Это всего лишь два метода.

1 голос
/ 23 апреля 2010

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

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

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

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

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

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

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

Вам нужно будет сделать различие между значениями r и lvalue, если оператор + принимает параметр дерева по значению

tree operator +(tree a, tree b);
...