Следующий блок кода появляется в известной книге Скотта Мейерса «Эффективный С ++» Пункт 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). (Увидеть мой предыдущий пост)
• Когда нам понадобится перегрузка для const T& и еще один для Т&&?
По сути, когда перемещение дает вам прирост производительности, должен быть и конструктор перемещения. То же самое относится и к функциям, в которых в противном случае вам понадобилась бы дорогая копия.
В вашем примере, где вы возвращаете ссылку на char
Однако не рекомендуется также устанавливать функцию, которая возвращается Rvalue ссылка. Скорее, возвращайте по значению и полагайтесь на способность компилятора применять RVO (см., Например, Вот)
• Есть ли похожий трюк, чтобы поделиться реализациями для const 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: Смотрите также эта тема, который решает ваш вопрос гораздо более тщательно.