移动语义与完美转发的区别
Move semantics and perfect forwarding difference
我已经从这个问题中得到了什么移动语义:什么是移动语义?
但是我仍然不明白什么是完美转发与移动语义的关系。
谁能用简单的英语和简单的例子解释一下什么是完全转发?
纯英文尝试
这个问题可能太复杂了,无法用简单的英语句子准确地描述,但是可以把完美转发看作是一种将传递给函数的临时值移动到另一个函数的方法,就好像第一个函数根本不存在一样,因此没有任何不必要的复制或赋值。c++ 11允许您在试图从中获取引用(r值或l值)时,通过引入一些r值(&&)和l值(&)对类型的引用之间的转换规则来实现这一点。
r值引用是c++ 11的一个特性,它们被设计用来解决移动语义和完美的转发问题
这是一个简单的英语解释,但如果你想彻底理解这个问题,我建议你阅读以下内容:
<标题>问题:我们希望传递给函数F
的一些临时值可以传递给另一个函数E
,而不需要复制或赋值。
尝试解决此问题
如果你试图通过引用传递它,比如
template<typename T> void F(T& a) { E(a); }
你将不能使用临时值(它们不是l值)
F(1, 2, 3); // Won't work
将引用声明为
const
延长了堆栈上临时对象的生命周期(历史上这样做是为了避免常见的悬空引用错误),因此下面的工作template<typename T> void E(const T& a) {} template<typename T> void F(const T& a) { E(a); }
,但缺点是您必须修改函数的签名以符合此解决方案
如果我们对E的签名感兴趣(它应该符合某些东西),而不是对F的签名感兴趣,我们可以使用
template<typename T> void E(T& a) {} template<typename T> void F(const T& a) { E(const_cast<T&>(a)); }
但是如果用real const调用它并得到非常量,那将触发未定义行为
一个不可维护的解决方案可能是定义所有你需要的变量
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)); }
但是随着参数数量的增加,组合的数量也会增加:这很可能变得不可维护
c++ 11定义了一些规则来声明
"[给定]类型TR是对类型T的引用,尝试创建类型"左值引用到cv TR"创建类型"左值",而试图创建类型"右值引用"to cv TR "创建类型TR。"
in human-form (TR = type T的引用,R = reference):
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)
这里重要的一点是,现在你可以跟踪函数接收到的类型:你可以接收到一个l值并将相同的l值传递给E,或者你可以接收到一个r值并将相同的r值传递给E(在将l值引用转换为任何类型引用之后):
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的博客和其他一些资源,不幸的是我再也找不到了。如果有人有关于这些的线索,请写在下面的评论中,我会更新帖子。谢谢。
处理r值引用和引用折叠可能比最初看起来更复杂。
完美转发
完美转发是为了确保提供给函数的参数被转发(传递)给具有最初提供的相同值类别(基本上是r值与l值)的另一个函数。
通常用于可能发生引用折叠的模板函数。
也可在同一函数中使用。
Scott 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);
}
}
例子strong>
上面的一个例子,一个典型的例子是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
一样,也就是说,参数的引用、l值和r值性质是保持不变的。
一个更具体的例子;
#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中一些新的基本语言结构,这些结构构成了我们现在在泛型编程中看到的大部分内容的基础:
- 参考崩溃
- 右值引用 <
- 移动语义/gh>
std::forward
的使用目前是在公式std::forward<T>
中使用的,理解std::forward
的工作原理有助于理解为什么会这样,还有助于识别非惯用的或不正确的右值使用,引用折叠和类似。
Thomas Becker提供了一篇关于完美转发问题和解决方案的好文章。
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- int(c) 和 c-'0' 之间的区别。C++
- 向量 <int> a {N, 0} 和 int arr a[N] = {0} 的时间复杂度有什么区别
- C++ - "!pointer"和"pointer == nullptr"的区别?
- C++ 使用 assign 函数的字符串与直接使用 '=' 更改值的字符串之间的区别
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 在 .h 文件中的类中声明静态变量和在.cpp文件中声明"global"变量有什么区别
- 我是C++编程的新手,这些代码之间有什么区别,我应该使用哪一个
- 在 const 函数中通过引用和指针返回之间的区别
- 我想知道长双倍和双倍之间的区别
- 正在折叠转发引用
- 返回常量对象引用 (getter) 和仅返回字符串有什么区别?
- 返回递归调用和仅递归调用的区别
- Qt:remove() 和 rmdir() 有什么区别
- 这 4 个 lambda 表达式之间有什么区别?
- 结构体 S { int align; } 之间的区别;(struct 关键字后的名称)和 struct { int al
- (double) 和 double() 之间的区别
- 在类内和类外的定义处执行类转发声明是否有区别
- 移动语义与完美转发的区别
- 在完美转发中,'decltype(std::forward<Args>(args))...' 和 Args&& 之间有什么区别