Мы начнем с функции перемещения (которую я немного очистил):
template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
return static_cast<typename remove_reference<T>::type&&>(arg);
}
Давайте начнем с более простой части - то есть, когда функция вызывается с помощью rvalue:
Object a = std::move(Object());
// Object() is temporary, which is prvalue
и наш шаблон move
создается следующим образом:
// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
return static_cast<remove_reference<Object>::type&&>(arg);
}
Поскольку remove_reference
преобразует T&
в T
или T&&
в T
, иObject
не является ссылкой, наша последняя функция:
Object&& move(Object&& arg)
{
return static_cast<Object&&>(arg);
}
Теперь вы можете задаться вопросом: нужен ли нам актерский состав?Ответ: да, мы делаем.Причина проста;именованная ссылка rvalue обрабатывается как lvalue (и неявное преобразование из ссылки lvalue в rvalue запрещено стандартом).
Вот что происходит, когда мы вызываем move
с lvalue:
Object a; // a is lvalue
Object b = std::move(a);
и соответствующий move
экземпляр:
// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
return static_cast<remove_reference<Object&>::type&&>(arg);
}
Опять remove_reference
преобразует Object&
в Object
и мы получаем:
Object&& move(Object& && arg)
{
return static_cast<Object&&>(arg);
}
Теперь перейдем к хитрой части: что вообще означает Object& &&
и как он может связываться с lvalue?
Чтобы обеспечить идеальную пересылку, стандарт C ++ 11 предоставляет специальные правила для свертывания ссылок, которые заключаются в следующем::
Object & & = Object &
Object & && = Object &
Object && & = Object &
Object && && = Object &&
Как видите, под этими правилами Object& &&
фактически означает Object&
, что является простой ссылкой на lvalue, которая позволяет связывать lvalue.
Таким образом, конечная функция:
Object&& move(Object& arg)
{
return static_cast<Object&&>(arg);
}
, что мало чем отличается от предыдущего создания экземпляра с rvalue - они оба приводят его аргумент к ссылке на rvalue, а затем возвращают его.Разница в том, что первый экземпляр может использоваться только с r-значениями, а второй - с l-значениями.
Чтобы объяснить, зачем нам нужно remove_reference
, немного больше, давайте попробуем эту функцию
template <typename T>
T&& wanna_be_move(T&& arg)
{
return static_cast<T&&>(arg);
}
и создайте его с помощью lvalue.
// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
return static_cast<Object& &&>(arg);
}
Применяя правила свертывания ссылок, упомянутые выше, вы можете увидеть, что мы получаем функцию, которая не может использоваться как move
(проще говоря, вы вызываете ееэто с lvalue, вы получите lvalue обратно).Во всяком случае, эта функция является функцией идентификации.
Object& wanna_be_move(Object& arg)
{
return static_cast<Object&>(arg);
}