Совместное использование реализаций для перегрузок const lvalue (const T&) и rvalue (T&&): точно так же, как это делается для константных и неконстантных перегрузок
Фон
Следующий блок кода появляется в известной книге Скотта Мейерса "Эффективный C++", пункт 3:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const
{
... // do bounds checking
... // log access data
... // verify data integrity
return text[position];
}
char& operator[](std::size_t position)
{
... // do bounds checking
... // log access data
... // verify data integrity
return text[position];
}
...
private:
std::string text;
};
Автор заявляет, что в приведенной выше реализации содержание const
и не const
перегрузки по сути одинаковы. Чтобы избежать дублирования кода, его можно упростить следующим образом:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // the same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls const op[]
{
return // cast away const on
const_cast<char&>( // op[]'s return type;
static_cast<const TextBlock&>(*this) // add const to *this's type;
[position] // call const version of op[]
);
}
...
private:
std::string text;
};
Вопросы
Мои вопросы:
Когда нам понадобится перегрузка для
const T&
и еще один дляT&&
? (Вот,T
может быть параметром шаблона или типом класса, такT&&
может означать или не означать универсальную ссылку) Я вижу, что в стандартной библиотеке многие классы обеспечивают обе перегрузки. Примерами являются конструкторыstd::pair
а такжеstd::tuple
Есть тонны перегрузок. (Хорошо, я знаю, что среди функций одна из них - конструктор копирования, а одна - конструктор перемещения.)Есть ли похожий трюк, чтобы поделиться реализациями для
const T&
а такжеT&&
Перегрузки? Я имею в виду, еслиconst T&&
Перегрузка возвращает объект, созданный копией, иT&&
Перегрузка возвращает что-то, что было сконструировано с перемещением, после совместного использования реализации это свойство все равно должно храниться. (Так же, как вышеупомянутый трюк:const
возвращаетсяconst
и неconst
возвращает неconst
как до, так и после реализации обмена)
Спасибо!
Разъяснения
Две перегрузки, о которых я говорю, должны выглядеть следующим образом:
Gadget f(Widget const& w);
Gadget f(Widget&& w);
Это не связано с возвратом по ссылкам rvalue, то есть:
Widget&& g(/* ... */);
(Кстати, этот вопрос был рассмотрен в моем предыдущем посте)
В f()
выше, если Gadget
является как копируемым, так и перемещаемым, нет никакого способа (кроме чтения реализации) определить, является ли возвращаемое значение созданным с помощью копирования или созданным с помощью перемещения. Нет ничего общего с оптимизацией возвращаемого значения (RVO) / оптимизацией именованного возврата (NRVO). (См. Мой предыдущий пост)
Рекомендации
1 ответ
• Когда нам понадобится перегрузка для const T& и другая для T&&?
По сути, когда перемещение дает вам прирост производительности, должен быть и конструктор перемещения. То же самое относится к функциям, в которых в противном случае вам понадобилась бы дорогая копия.
В вашем примере, где вы возвращаете ссылку на char
Однако не рекомендуется также устанавливать функцию, которая возвращает ссылку на значение. Скорее, возвращайте по значению и полагайтесь на способность компилятора применять RVO (см., Например, здесь)
• Есть ли подобный прием для совместного использования реализаций для постоянных перегрузок T & и T&&?
Я часто находил полезным установить конструктор или функцию, используя универсальную ссылку (я ленивый), то есть что-то вроде
struct MyClass
{
template<typename T /*, here possibly use SFINAE to allow only for certain types */>
MyClass(T&& _t) : t(std::forward<T>(_t)) {}
private:
SomeType t;
};
РЕДАКТИРОВАТЬ: Относительно вашего обновления: если у вас есть дорогая копия Widget
в вашей функции f
Рекомендуется также обеспечить перегрузку, принимая Widget&&
,
Gadget f(Widget const& w)
{
Widget temp = w; //expensive copy
}
Gadget f(Widget&& w)
{
Widget temp = std::move(w); //move
}
Вы можете объединить обе функции, используя такой шаблон функции
template<typename WidgetType
// possibly drop that SFINAE stuff
// (as it is already checked in the first assignment)
, typename std::enable_if<std::is_convertible<std::remove_reference_t<WidgetType>, Widget>::value> >
Gadget(WidgetType&& w)
{
Widget temp = std::forward<WidgetType>(w);
//or
std::remove_reference_t<WidgetType> temp2 = std::forward<WidgetType>(w);
}
... Я не сказал, что это лучше;-).
РЕДАКТИРОВАТЬ 2: См. Также эту ветку, которая рассматривает ваш вопрос гораздо более тщательно.