c++ 将引用赋值给引用_深入浅出 C++ 11 右值引用

本文介绍了C++ 11中的右值引用、移动语义及其常见误解,如错误地移动局部变量、认为被移动的值不可用等。文章详细阐述了值类别、左值引用与右值引用的区别,以及移动语义在资源管理中的作用,强调了移动构造函数的重要性。同时,探讨了拷贝省略、通用引用和完美转发的概念,帮助开发者更好地理解和利用这些特性提高代码效率。
摘要由CSDN通过智能技术生成

9f69bfb648e1a3654303941f3c097731.png
彻底搞清楚:右值引用/移动语义/拷贝省略/通用引用/完美转发 —— 以最短的篇幅,介绍常见误解(什么时候要用 move?什么时候不能 move?为什么 move 失败?)和基础知识(为什么右值引用变量是左值?为什么会调用移动构造函数?),一步步解释“为什么/是什么/怎么做”。

写在前面

如果你还不知道 C++ 11 引入的右值引用是什么,可以读读这篇文章,看看有什么 启发;如果你已经对右值引用了如指掌,也可以读读这篇文章,看看有什么 补充

尽管 C++ 17 标准已经发布了,很多人还不熟悉 C++ 11 的 右值引用/移动语义/拷贝省略/通用引用/完美转发 等概念,甚至对一些细节 有所误解(包括我 [emoji])。

本文将以最短的篇幅,一步步解释 关于右值引用的 为什么/是什么/怎么做。先分享几个我曾经犯过的错误。

误解:返回前,移动局部变量

ES.56: Write std::move() only when you need to explicitly move an object to another scope
std::string base_url = tag->GetBaseUrl();
if (!base_url.empty()) {
  UpdateQueryUrl(std::move(base_url) + "&q=" + word_);
}
LOG(INFO) << base_url;  // |base_url| may be moved-from

上述代码的问题在于:使用 std::move() 移动局部变量 base_url,会导致后续代码不能使用该变量;如果使用,会出现 未定义行为 (undefined behavior)(参考:std::basic_string(basic_string&&))。

如何检查 移动后使用 (use after move)

  • 运行时,在移动构造函数中,将被移动的值设置为无效状态,并在每次使用前检查有效性
  • 编译时,使用 Clang 标记对移动语义进行静态检查(参考:Consumed Annotation Checking | Attributes in Clang)

误解:被移动的值不能再使用

C.64: A move operation should move and leave its source in a valid state

很多人认为:被移动的值会进入一个 非法状态 (invalid state),对应的 内存不能再访问

其实,C++ 标准要求对象 遵守 [sec|移动语义] 移动语义 —— 被移动的对象进入一个 合法但未指定状态 (valid but unspecified state),调用该对象的方法(包括析构函数)不会出现异常,甚至在重新赋值后可以继续使用:

auto p = std::make_unique<int>(1);
auto q = std::move(p);

assert(p == nullptr);  // OK: reset to default
p.reset(new int{2});   // or p = std::make_unique<int>(2);
assert(*p == 2);       // OK: reset to int*(2

另外,基本类型(例如 int/double)的移动语义 和拷贝相同:

int i = 1;
int j = std::move(i);

assert(i == j)

误解:移动非引用返回值

F.48: Don’t return std::move(local)
std::unique_ptr<int> foo() {
  auto ret = std::make_unique<int>(1);
  //...
  return std::move(ret);  // -> return ret;
}

上述代码的问题在于:没必要使用 std::move() 移动非引用返回值。

C++ 会把 即将离开作用域的 非引用类型的 返回值当成 右值(参考 [sec|值类别 vs 变量类型]),对返回的对象进行 [sec|移动语义] 移动构造(语言标准);如果编译器允许 [sec|拷贝省略] 拷贝省略,还可以省略这一步的构造,直接把 ret 存放到返回值的内存里(编译器优化)。

Never apply std::move() or std::forward() to local objects if they would otherwise be eligible for the return value optimization. —— Scott Meyers, Effective Modern C++

另外,误用 std::move()阻止 编译器的拷贝省略 优化。不过聪明的 Clang 会提示 -Wpessimizing-move/-Wredundant-move 警告。

误解:不移动右值引用参数

F.18: For “will-move-from” parameters, pass by X&& and std::move() the parameter
std::unique_ptr<int> bar(std::unique_ptr<int>&& val) {
  //...
  return val;    // not compile
                 // -> return std::move/forward(val);
}

上述代码的问题在于:没有对返回值使用 std::move()(编译器提示 std::unique_ptr(const std::unique_ptr&) = delete 错误)。

If-it-has-a-name Rule:
  • Named rvalue references are lvalues.
  • Unnamed rvalue references are rvalues.

因为不论 左值引用 还是 右值引用 的变量(或参数)在初始化后,都是左值(参考 [sec|值类别 vs 变量类型]):

  • 命名的右值引用 (named rvalue reference) 变量左值&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值