шаблоны — Избегайте универсальных ссылок в правильной реализации Скотта Мейера. Более эффективный элемент C ++ 22: & quot; Рассмотрите возможность использования op = вместо автономного op & quot ;?

Я пытаюсь следовать совету Скотта Мейерса в пункте 22 «Более эффективного C ++»: «Подумайте об использовании op= вместо автономного op«Он предлагает, чтобы мы могли создать шаблон для operator+, так что все классы, которые реализуют operator+= автоматически получить operator+:

template<class T>
const T operator+(const T& lhs, const T& rhs)
{
return T(lhs) += rhs;
}

Теперь, в пункте 25 Effective Modern C ++, есть пример (стр. 172) добавления матрицы, где перегрузки operator+ с R-значениями, потому что, если вы знаете, что lhs или же rhs являются значениями, вы можете использовать их для хранения результата, предотвращая бесполезные копии, возможно, огромных матриц. Поэтому я добавил перегрузки:

template<class T>
T operator+(T&& lhs, const T& rhs)
{
return std::move(lhs += rhs);
}

template<class T>
T operator+(T const& lhs, T&& rhs)
{
return std::move(rhs += lhs);
}

template<class T>
T operator+(T&& lhs, T&& rhs)
{
return std::move(lhs += rhs);
}

Проблема здесь в том, что T&& это универсальная ссылка, и она в конечном итоге захватывает все, поэтому я в итоге перехожу к lvalues, что является нежелательным поведением.

Так как я могу сделать правильную реализацию operator+ шаблон?

Частичное решение с использованием передачи по значению: Я также прочитал статью 41: «Рассмотрите возможность передачи по значению для копируемых параметров …» из Effective Modern C ++, поэтому я попытался написать свою собственную версию следующим образом:

template<class T>
const T operator-(T lhs, T rhs)
{
return lhs -= rhs;
}

Но это упускает возможность оптимизации, когда rhs является значением r, поскольку в этом случае я не буду использовать rhs для сохранения результата. Так что это только частичное решение.

1

Решение

Я думаю, что ваша проблема в том, что вы пытаетесь объединить советы C ++ 03 (написать общий operator+) с хорошим советом по C ++ 11 (перегрузка operator+ для значений), и два совета не обязательно совместимы.

Универсальный operator+ что вы хотите написать, это сложно сделать правильно с пересылкой ссылок, но вы могли бы использовать тот же подход, что и оригинал, и создать временный, переходя от lhs если это значение, используя std::forward («универсальные ссылки» теперь известны как пересылка ссылок и это должно дать вам понять, что лучший способ борьбы с ними, как правило, std::forward не std::move):

template<class T>
T operator+(T&& lhs, const T& rhs)
{
T tmp{ std::forward<T>(lhs) };
tmp += rhs;
return tmp;
}

Обратите внимание, что это будет только принять значения в левой части, потому что другой параметр означает, что T не будет выводиться как ссылочный тип, такой как X& но только тип объекта, такой как Xпервый параметр может соответствовать только значениям r.

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

template<class T>
T operator+(const T& lhs, const T& rhs)
{
T tmp{lhs};
tmp += rhs;
return tmp;
}

Это может быть не совсем оптимальным, потому что ни одна перегрузка не будет использовать повторно rhs когда это значение, но, как я уже сказал в комментарии выше rhs += lhs не правильно, если сложение не является коммутативным. Если вы счастливы предположить, что это добирается, то вы могли бы улучшить это для повторного использования rhs когда это значение (добавляя больше перегрузок и больше сложности).

Лично я думаю, что общий operator+ определяется с точки зрения operator+= Это интересное упражнение, но не очень практичное, особенно если принять во внимание семантику перемещения. Написание кастома operator+ для вашего типа (как тот, что на p172 в More Effective C ++) не тот много работы, и вам не придется иметь дело с пересылкой ссылок, и вы будете знать, коммутирует ли сложение для этого типа и безопасно ли это делать rhs += lhs или нет.

3

Другие решения


По вопросам рекламы [email protected]