Устранение временных перегрузок операторов - PullRequest
4 голосов
/ 25 сентября 2010

Примечание: , как отмечает selibitze. Я не совсем в курсе ссылок на rvalues, поэтому предлагаемые мною методы содержат ошибки, прочитайте его ответ, чтобы понять, какие именно.

Я былчитая вчера одну из разглагольствования Линуса и (где-то) разглагольствует против перегрузки операторов.

Похоже, что если у вас есть объект типа S, то:

S a = b + c + d + e;

может включать много временных.

В C ++ 03 у нас есть копия elision, чтобы предотвратить это:

S a = ((b + c) + d) + e;

Я надеюсь, что последний... + e оптимизирован, но мне интересно, сколько временных созданий создается с пользовательским определением operator+.

Кто-то в теме предложил использовать шаблоны выражений для решения этой проблемы.

Сейчасэтот поток восходит к 2007 году, но в настоящее время, когда мы думаем об исключении временных, мы думаем Move.

Так что я думал о наборе операторов перегрузки, которые мы должны написать не для устранения временных, а для ограничениястоимость of их конструкция (кража ресурсов).

S&& operator+(S&& lhs, S const& rhs) { return lhs += rhs; }
S&& operator+(S const& lhs, S&& rhs) { return rhs += lhs; } // *
S&& operator+(S&& lhs, S&& rhs) { return lhs += rhs; }

Достаточно ли этого набора операторов?Это обобщаемо (по вашему мнению)?

*: эта реализация предполагает коммутативность, она не работает для печально известного string.

1 Ответ

4 голосов
/ 25 сентября 2010

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

S operator+(S const& lhs, S const& rhs);
S operator+(S     && lhs, S const& rhs);
S operator+(S const& lhs, S     && rhs);
S operator+(S     && lhs, S     && rhs);

Функции возвращают prvalue вместо xvalue . Возвращение xvalues ​​ обычно очень опасная вещь & ndash; std :: move и std :: forward - очевидные исключения. Если бы вы возвращали ссылку на rvalue, вы бы взломали код вроде:

for (char c : my_string + other_string) {
   //...
}

Этот цикл ведет себя (согласно 6.5.4 / 1 в N3092), как если бы код был:

auto&& range = my_string + other_string;

Это, в свою очередь, приводит к зависанию. Время жизни временного объекта не увеличивается, потому что ваш оператор + не возвращает prvalue . Возврат объектов по значению прекрасно. Это создаст временные объекты, но эти объекты являются значениями, поэтому мы можем украсть их ресурсы, чтобы сделать его очень эффективным.

Во-вторых, ваш код также не должен компилироваться по той же причине, по которой он не будет компилироваться:

int&& foo(int&& x) { return x; }

Внутри тела функции x является lvalue, и вы не можете инициализировать «возвращаемое значение» (в данном случае ссылку rvalue) с помощью выражения lvalue. Итак, вам нужно явное приведение.

В-третьих, вам не хватает const & + const & overload. Если оба ваших аргумента являются lvalues, компилятор не найдет используемый оператор + в вашем случае.

Если вы не хотите так много перегрузок, вы также можете написать:

S operator+(S value, S const& x)
{
   value += x;
   return value;
}

Я специально не писал return value+=x;, потому что этот оператор, вероятно, возвращает ссылку на lvalue, которая привела бы к созданию копии возвращаемого значения. С двумя написанными мной строками возвращаемое значение будет построено из значения .

S x = a + b + c + d;

По крайней мере, этот случай очень эффективен, поскольку нет необходимости в копировании, даже если компилятор не может удалить копии & ndash; благодаря классу строк с поддержкой перемещения. На самом деле, с таким классом, как std :: string, вы можете использовать функцию-член быстрой замены и сделать ее эффективной и в C ++ 03, при условии, что у вас есть достаточно умный компилятор (например, GCC):

S operator+(S value, S const& x) // pass-by-value to exploit copy elisions
{
   S result;
   result.swap(value);
   result += x;
   return result; // NRVO applicable
}

См. Статью Дэвида Абрахама Хотите скорость? Передать по значению . Но эти простые операторы не будут такими эффективными, учитывая:

S x = a + (b + (c + d));

Здесь левая часть оператора всегда lvalue. Так как оператор + занимает левую часть по значению, это приводит ко многим копиям Четыре перегрузки сверху прекрасно справляются и с этим примером.

Прошло много времени с тех пор, как я прочитал старую напыщенную речь Линуса. Если он жаловался на ненужные копии в отношении std :: string, эта жалоба больше не действительна в C ++ 0x, но раньше она была недействительной. Вы можете эффективно объединить много строк в C ++ 03:

S result = a;
result += b;
result += c;
result += d;

Но в C ++ 0x вы также можете использовать операторы + и std :: move. Это тоже будет очень эффективно.

Я действительно посмотрел исходный код Git и его управление строками (strbuf.h). Это выглядит хорошо продуманным. За исключением функции отсоединения / присоединения, вы получаете то же самое с std :: string с включенным перемещением, с очевидным преимуществом в том, что ресурс автоматически управляется самим классом по сравнению с пользователем, которому нужно помнить, чтобы вызывать нужные функции в правильные времена (strbuf_init, strbuf_release).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...