Идеальная переадресация
Что подразумевается под идеальной пересылкой?
«Пересылка — это процесс, при котором одна функция передает свой параметр другой функции.
При идеальной пересылке функция должна получить тот же объект, который был передан от функции, выполняющей пересылку.
Другими словами, идеальная пересылка означает, что мы не просто передаем объекты, мы также передаем их важные свойства, будь то lvalues или rvalues, const или volatile.
Не беспокойтесь слишком много об определении, мы рассмотрим несколько простых примеров.
Допустим, у нас есть простой класс, как показано ниже
class Object {
public:
Object() = default;
void SetName(const std::string &name) { name_ = std::move(name); }
std::string GetName() const { return name_; }
private:
std::string name_;
};
У нас также есть несколько перегруженных функций UseObject
.
void UseObject(Object &) {
std::cout << "calling UseObject(Object &)" << std::endl;
}
void UseObject(const Object &) {
std::cout << "calling UseObject(const Object &)" << std::endl;
}
void UseObject(Object &&) {
std::cout << "calling UseObject(Object &&)" << std::endl;
}
Теперь у нас есть главная
int main() {
Object object;
const Object const_object;
UseObject(object);
UseObject(const_object);
UseObject(std::move(object));
}
что приведет к следующему результату
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
Сейчас, допустим, у нас есть простая шаблонная функция, которая пытается передать аргумент функции UseObject
.
template <typename T>
void NotForwardToUseObject(T x) {
UseObject(x);
}
Теперь, запуск кода
int main() {
Object object;
const Object const_object;
NotForwardToUseObject(object);
NotForwardToUseObject(const_object);
NotForwardToUseObject(std::move(object));
}
приведет к тому, что
calling UseObject(Object &)
calling UseObject(Object &)
calling UseObject(Object &)
где функции вызываются не так, как мы ожидали ранее.
Это происходит из-за того, что const
и rvalueness
игнорируются вычитанием шаблона для void NotForwardToUseObject(T x)
.
Для работы со ссылочными параметрами мы должны использовать универсальные ссылки, потому что только универсальные ссылочные параметры кодируют информацию о lvalueness и rvalueness аргументов, которые им передаются.
Теперь, если мы используем универсальную ссылку для аргумента шаблона,
template <typename T>
void HalfForwardToUseObject(T &&x) { // universal reference
UseObject(x);
}
Выполнение кода
int main() {
Object object;
const Object const_object;
HalfForwardToUseObject(object);
HalfForwardToUseObject(const_object);
HalfForwardToUseObject(std::move(object));
}
приведет к тому, что
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &)
Почти! Пересылка const
вроде бы работает, но rvalueness
аргумента все еще не пересылается правильно.
Чтобы получить действительно идеальную пересылку, мы должны привести x
к его исходному типу и l- или r-значению.
template <typename T>
void ForwardToUseObject(T &&x) {
UseObject(static_cast<T &&>(x));
}
Теперь, выполнение кода
int main() {
Object object;
const Object const_object;
ForwardToUseObject(object);
ForwardToUseObject(const_object);
ForwardToUseObject(std::move(object));
}
приведет к тому, что
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
Отлично! Мы успешно передали объект должным образом.
Чтобы упростить код, мы можем использовать std::forward
из библиотеки C++ <utility>
,
template <typename T>
void PerfectForwardToUseObject(T &&x) {
UseObject(std::forward<T>(x));
}
Надеюсь, что с помощью приведенных выше примеров вы поняли, что подразумевается под идеальной переадресацией.
Как обычно, приведенный выше код доступен с моего github.
Спасибо, что дочитали до конца!