理解完美转发
Understanding perfect forwarding
据我了解,作为参数传递给函数的右值变为左值,
std::forward return 如果参数作为右值传递,则为右值;如果作为左值传递,则为左值。这是我的 class:
#include <string>
#include <iostream>
struct MyClass
{
MyClass()
{
std::cout << "default";
}
MyClass(const MyClass& copy)
{
std::cout << "copy";
}
MyClass& operator= (const MyClass& right)
{
std::cout << "=";
return *this;
}
MyClass& operator= (const MyClass&& right)
{
std::cout << "mov =";
return *this;
}
MyClass(MyClass&& mov)
{
std::cout << "mov constructor";
}
};
void foo(MyClass s)
{
MyClass z = MyClass(std::forward<MyClass>(s));
}
void main()
{
auto a = MyClass();
foo(MyClass()); //z is created by move_constructor
foo(a); //z is created by move_constructor, but I think it must be created using copy constructor
}
我的问题是:为什么在这两种情况下都使用 move_constructor 创建 z 变量。
我认为必须在第一种情况下移动 foo(MyClass()) 并在第二种情况下复制 foo(a)。在第二种情况下,我将左值作为参数 s 传递,并且 std::forward 必须 return 左值,然后将其作为左值引用传递给 MyClass 构造函数。我哪里错了?
您声明的参数类型是MyClass
。初始化参数的表达式与您的函数无关 - 它不会影响参数的类型。
MyClass
不是引用类型。 std::forward
将非引用类型的左值表达式转换为右值。 std::forward
在此上下文中的使用等同于 std::move
.
请注意,参数本身是在调用中复制构造的 foo(a)
。
我看你已经够糊涂了。 forward 的作用是 only 当 universal references 发挥作用时,universal reference 就像 T&& t
但 only 当 T 是模板参数。
例如void foo(X&& x);
中的x
不是转发引用,是普通的右值引用,转发没有意义。相反,如果你想保留它的右值,你可以使用 std::move
,否则它会变成左值:
void foo(X&& x) {
bar(x); // calls bar with an l-value x, x should be not moved from
baz(std::move(x)); // calls bar with an r-value x, x is likely moved from after this and probably unusable
}
换句话说,上面的函数 foo
是专门为将右值引用作为参数而设计的,不会接受任何其他内容。作为函数编写者,您以这种方式定义了它的契约。
相反,在template <class T> void foo(T&& t)
这样的上下文中,t
是转发引用。由于引用折叠规则,它可能是右值或左值引用,具体取决于在调用站点处赋予函数 foo 的表达式的值。在这种情况下,您使用
template<class T>
void foo(T&& t) {
// bar is called with value matching the one at the call site
bar(std::forward<T>(t));
}
据我了解,作为参数传递给函数的右值变为左值, std::forward return 如果参数作为右值传递,则为右值;如果作为左值传递,则为左值。这是我的 class:
#include <string>
#include <iostream>
struct MyClass
{
MyClass()
{
std::cout << "default";
}
MyClass(const MyClass& copy)
{
std::cout << "copy";
}
MyClass& operator= (const MyClass& right)
{
std::cout << "=";
return *this;
}
MyClass& operator= (const MyClass&& right)
{
std::cout << "mov =";
return *this;
}
MyClass(MyClass&& mov)
{
std::cout << "mov constructor";
}
};
void foo(MyClass s)
{
MyClass z = MyClass(std::forward<MyClass>(s));
}
void main()
{
auto a = MyClass();
foo(MyClass()); //z is created by move_constructor
foo(a); //z is created by move_constructor, but I think it must be created using copy constructor
}
我的问题是:为什么在这两种情况下都使用 move_constructor 创建 z 变量。 我认为必须在第一种情况下移动 foo(MyClass()) 并在第二种情况下复制 foo(a)。在第二种情况下,我将左值作为参数 s 传递,并且 std::forward 必须 return 左值,然后将其作为左值引用传递给 MyClass 构造函数。我哪里错了?
您声明的参数类型是MyClass
。初始化参数的表达式与您的函数无关 - 它不会影响参数的类型。
MyClass
不是引用类型。 std::forward
将非引用类型的左值表达式转换为右值。 std::forward
在此上下文中的使用等同于 std::move
.
请注意,参数本身是在调用中复制构造的 foo(a)
。
我看你已经够糊涂了。 forward 的作用是 only 当 universal references 发挥作用时,universal reference 就像 T&& t
但 only 当 T 是模板参数。
例如void foo(X&& x);
中的x
不是转发引用,是普通的右值引用,转发没有意义。相反,如果你想保留它的右值,你可以使用 std::move
,否则它会变成左值:
void foo(X&& x) {
bar(x); // calls bar with an l-value x, x should be not moved from
baz(std::move(x)); // calls bar with an r-value x, x is likely moved from after this and probably unusable
}
换句话说,上面的函数 foo
是专门为将右值引用作为参数而设计的,不会接受任何其他内容。作为函数编写者,您以这种方式定义了它的契约。
相反,在template <class T> void foo(T&& t)
这样的上下文中,t
是转发引用。由于引用折叠规则,它可能是右值或左值引用,具体取决于在调用站点处赋予函数 foo 的表达式的值。在这种情况下,您使用
template<class T>
void foo(T&& t) {
// bar is called with value matching the one at the call site
bar(std::forward<T>(t));
}