До c ++ 11 я писал такой код:
// Small functions
void doThingsWithA(const A& a)
{
// do stuff
}
void doThingsWithB(const B& b)
{
// do stuff
}
void doThingsWithC(const C& c)
{
// do stuff
}
// Big function
void doThingsWithABC(const A& a, const B& b, const C& c)
{
// do stuff
doThingsWithA(a);
doThingsWithB(b);
doThingsWithC(c);
// do stuff
}
Но теперь, с семантикой перемещения, может стать интересным (по крайней мере, в некоторых случаях), чтобы мои функции могли принимать rvalue ссылки в качестве параметров и добавлять эти перегрузки:
void doThingsWithA(A&& a);
void doThingsWithB(B&& b);
void doThingsWithC(C&& c);
Из того, что я понял, если я хочу вызывать эти перегрузки в моей большой функции, мне нужно использовать идеальную переадресацию, которая может выглядеть следующим образом (это немного менее читабельно, но я думаю, что это может быть хорошо с хорошее соглашение об именах для типов шаблонов):
template<typename TplA, typename TplB, typename TplC>
void doThingsWithABC(TplA&& a, TplB&& b, TplC&& c)
{
// do stuff
doThingsWithA(std::forward<TplA>(a));
doThingsWithB(std::forward<TplB>(b));
doThingsWithC(std::forward<TplC>(c));
// do stuff
}
Моя проблема заключается в следующем: не означает ли это, что если мои маленькие функции имеют другие перегрузки, станет возможным вызывать большую с параметрами типов, для которых она не предназначена?
Я думаю, что это может помочь предотвратить это:
template<typename TplA, typename TplB, typename TplC,
class = typename std::enable_if<std::is_same<A, std::decay<TplA>::type>::value>::type,
class = typename std::enable_if<std::is_same<B, std::decay<TplB>::type>::value>::type,
class = typename std::enable_if<std::is_same<C, std::decay<TplC>::type>::value>::type>
doThingsWithABC(TplA&& a, TplB&& b, TplC&& c)
{
// do stuff
doThingsWithA(std::forward<TplA>(a));
doThingsWithB(std::forward<TplB>(b));
doThingsWithC(std::forward<TplC>(c));
// do stuff
}
Хотя я не уверен, что это не слишком ограничительно, поскольку я понятия не имею, как он себя ведет, если я пытаюсь вызывать большие функции с типами, которые неявно преобразуются в A, B или C …
Но … даже если предположить, что это работает, у меня действительно нет других вариантов? (Я имею в виду … это не просто на глазах)
Идеальная пересылка предназначена главным образом для случаев, когда вы не знаете, как будут использоваться данные, потому что вы пишете универсальную оболочку «пользовательских» данных.
В простой процедурной системе, как вы описали выше, 3 вещи, которые вы делаете, будут конкретными задачами.
Это означает, что вы будете знать, выиграют ли они от наличия передвижного источника данных или нет, и будут ли они иметь смысл, если им придется копировать, и если перемещение будет дешевым.
Если копирование имеет смысл, но перемещение происходит быстрее, а перемещение дешево (обычный случай), они должны принимать параметры по значению и выйти из них, когда они хранят свою локальную копию.
Затем это правило применяется рекурсивно к функции, которая вызывает 3 подфункции.
Если функция не приносит пользы от перемещения, возьмите const&
,
Если копирование не имеет смысла, берите по rvalue-ссылке (не универсальной ссылке) или по значению.
В случае, когда это хорошо, чтобы иметь возможность move
а также move
остается дорогим, если вы считаете идеальной пересылку. Как отмечалось выше, это обычно происходит только при переносе функций, заданных «пользователем» вашей кодовой базы, как обычно move
или действительно очень дешево, или так же дорого, как копия. Вы должны быть в промежуточной или неопределенной стадии move
эффективность для идеальной пересылки, чтобы быть стоящей.
Существуют и другие способы идеальной пересылки, такие как контейнерные мутаторы, но они более эзотерические. В качестве примера мой backwards
Мутатор диапазонов прекрасно перенаправит входящий диапазон в хранилище, чтобы обеспечить правильную работу эталонного продления времени жизни, когда вы объединяете несколько мутаторов диапазонов в C ++ 11 в стиле ранжирования for(:)
петли.
Безумно совершенная пересылка приводит к раздутию сгенерированного кода, медленным построениям, утечкам реализации и сложному для понимания коду.
использование static_assert
с вместо enable_if
, ИМХО, эта опция не только проще для глаз, но и более удобна для пользователя. Компилятор выведет ясное сообщение об ошибке, если типы аргументов нарушены, тогда как с enable_if
коллега будет жаловаться на то, что соответствующая функция не найдена.
template<typename TplA, typename TplB, typename TplC>
void doThingsWithABC(TplA&& a, TplB&& b, TplC&& c)
{
static_assert(std::is_same<A, std::decay<TplA>::type>::value, "arg1 must be of type A");
static_assert(std::is_same<B, std::decay<TplB>::type>::value, "arg2 must be of type B");
static_assert(std::is_same<C, std::decay<TplC>::type>::value, "arg3 must be of type C");
// do stuff
doThingsWithA(std::forward<TplA>(a));
doThingsWithB(std::forward<TplB>(b));
doThingsWithC(std::forward<TplC>(c));
// do stuff
}