我已经从这个问题中了解了移动语义是什么: What are move semantics?
但是我还不知道与移动语义相关的完美转发是什么。
有人能用简单的英语和一个简单的例子解释一下什么是完美转发吗?
我已经从这个问题中了解了移动语义是什么: What are move semantics?
但是我还不知道与移动语义相关的完美转发是什么。
有人能用简单的英语和一个简单的例子解释一下什么是完美转发吗?
The problem is too complex to be accurately described by plain English sentences, but perfect forwarding can be thought of as a way to pass temporary values to another function without any unnecessary copies or assignments, as if the first function never existed. C++11 introduced conversion rules between r-value (&&) and l-value (&) references to allow for this.
C++11's r-value references address both move semantics and perfect forwarding issues.
This plain-English explanation only scratches the surface of the issue. For a more thorough understanding, it is suggested to read further:
In passing temporary values to function F
, there is a desire to avoid copying and assigning values in order to pass them to function E
.
If you try to pass it by reference like
template<typename T> void F(T& a) { E(a); }
you will not be able to use temporaries (they're not l-values)
F(1, 2, 3); // Won't work
Declaring a reference as const
prolongs the lifetime of a temporary on the stack (this was historically done to avoid a common dangling reference error) so the following works
template<typename T> void E(const T& a) {}
template<typename T> void F(const T& a) {
E(a);
}
but the downside is that you'll have to modify the signature of the function(s) to conform to this solution
If we're interested in the signature of E (it should conform to something) but not in F's one, we might get away with
template<typename T> void E(T& a) {}
template<typename T> void F(const T& a) {
E(const_cast<T&>(a));
}
but in case this gets called with a real const and gets un-constant'ed, that would trigger undefined behavior
An unmaintainable solution could be to define all the variants you need
template<typename T> void E(T& a) {}
template<typename T> void F(T& a) { E(a); }
template<typename T> void F(const T& a) { E(const_cast<T&>(a)); }
but as the number of parameters grow, the number of combinations grows as well: this is likely to become unmaintainable
C++11定义了一些规则,规定:
"[给定]一个类型TR是T类型的引用,试图创建类型“对cv TR的左值引用”会创建类型“对T的左值引用”,而试图创建类型“对cv TR的右值引用”会创建类型TR。"
以人类语言表述(TR = 类型T的引用,R = 引用):
TR R
T& & -> T& // an lvalue reference to cv TR (becomes)-> lvalue reference to T
T& && -> T& // an rvalue reference to cv TR (becomes)-> TR (lvalue reference to T)
T&& & -> T& // an lvalue reference to cv TR (becomes)-> lvalue reference to T
T&& && -> T&& // an rvalue reference to cv TR (becomes)-> TR (rvalue reference to T)
template<typename T> void E(T&& a) {}
template<typename T> void F(T&& a) { E(static_cast<T&&>(a)); }
static_cast<T&&>(a)
是
std::forward<T>(a); // is the same as static_cast<T&&>(a);
所以最终解决问题且让您的生活更轻松的代码是:
template<typename T> void E(T&& a) {}
template<typename T> void F(T&& a) { E(std::forward<T>(a)); }
参考资料:Herb Sutter的博客和其他一些来源,不幸的是我找不到它们了。如果有人知道,请在下面的评论中写下来,我会更新这篇文章。谢谢。
template<typename T> void F(T&& a) { E(a); }
不也可以吗?通过说std::forward<T>(a);
,我们向编译器提供了哪些额外信息? - j00hiScott Meyers在他的Going Native 2013演示中给出了以下伪代码,以解释std::forward
的工作原理(大约在20分钟的时候);
template <typename T>
T&& forward(T&& param) { // T&& here is formulated to disallow type deduction
if (is_lvalue_reference<T>::value) {
return param; // return type T&& collapses to T& in this case
}
else {
return move(param);
}
}
例子
来自上述网站的一个例子,典型的例子是 make_unique
template<class T, class... U>
std::unique_ptr<T> make_unique(U&&... u)
{
return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}
unique_ptr
的参数是通过 make_unique
直接提供给它的,就好像这些参数直接提供给了 unique_ptr
一样,即保持了参数的引用、左值和右值特性。#include <iostream>
#include <utility>
#include <memory>
struct A {
// implementation excluded
};
struct B {
B(A &) // ctor 1
{
std::cout << "ctor 1" << std::endl;
}
B(A&&) // ctor 2
{
std::cout << "ctor 2" << std::endl;
}
};
int main()
{
A a;
auto b1 = std::make_unique<B>(a); // ctor 1 is used
auto b2 = std::make_unique<B>(A()); // ctor 2 is used
}
简述
完美转发依赖于一些 C++11 中新引入的基础语言构造,这些构造是我们现在看到的大多数泛型编程的基础:
目前使用 std::forward
的方式是公式化的 std::forward<T>
,了解 std::forward
的工作原理有助于理解为什么这样做,并帮助识别 rvalue、引用折叠等非成语化或不正确的使用。
Thomas Becker 在完美转发问题和解决方案上提供了一个简洁但密集的 写作。
T&&
:( - fredoverflow