C/C++学习记录:std::move 源码分析

  • 抽空扣一点感兴趣的标准库源码,这里总结一下 std::move() 相关的分析
  • 本文中 gcc version: 8.4.1 20200928 (Red Hat 8.4.1-1) (GCC)
  • 其中c++库安装路径为 /usr/include/c++/8

目录

  • 一、源码与分析
    • 1. std::move 源码总览
    • 2. std::remove_reference 源码分析
    • 3. static_cast 分析
    • 4. std::move 分析
    • 5. std::move 中的引用折叠
  • 二、总结

一、源码与分析

1. std::move 源码总览

  std::move() 的定义位于 /usr/include/c++/8/bits/move.h 中,详细内容如下:
C/C++学习记录:std::move 源码分析_第1张图片  可以看到 std::move 的定义只有短短4行…

  第二行和第三行关键字 constexprnoexcept 都是c++11的新增关键字。其中 constexpr 把后面的 typename 声明为常量表达式,便于编译器对代码进行优化 。而 noexcept 则声明此函数不会抛出异常,遇到问题直接调用 std::terminate 退出进程。这两个关键字可以说是为规范和优化 std::move 而存在的,对其实现并无参与,所以这里跳过这俩关键字,不做过多分析。

  此外还存在两个东西:类型提取结构体 std::remove_reference 和 C++标准转换运算符static_cast 下面单独进行分析。

2. std::remove_reference 源码分析

  std::remove_reference 的定义位于 /usr/include/c++/8/type_traits 中,详细内容如下:
C/C++学习记录:std::move 源码分析_第2张图片
  可以看到,std::remove_reference 结构体的实现非常简单,功能就是依靠模板把传参 _Tp 的类型分离出来,当调用 std::remove_reference::type 时即为分离出的最底层类型。

  • 测试如下内容:
    C/C++学习记录:std::move 源码分析_第3张图片
    结果如下:
    C/C++学习记录:std::move 源码分析_第4张图片
    说明 std::remove_reference 可以很好的将类型提取出来,即 int&int&& 都可以提取出基础类型 int

3. static_cast 分析

  static_cast 也是c++11中的新特性,简单来说用处就是类型转换。语法为:

static_cast<新类型>(表达式)

  其返回值为 “新类型” 类型的值,例如 n = static_cast(3.14) 后,此时 n = 3。我个人认为可以粗略的将 static_cast 当作一个更高级的强制类型转换,相比传统的强制类型转换,static_cast 会对转换类型进行检测,所以相对更加安全。
  可以说,任何具有明确定义的类型转换,只要不包含底层const,都可以使用 static_cast

4. std::move 分析

  由上文可知两个关键内容的作用,则可首先带入一个实例来化简分析 std::move 的实际作用。

首先是一小部分代码:

std::string s = "Hello";
std::vector<std::string> v;
v.push_back(std::move(s));

根据 std::move 的流程,std::move(s) 中的 return 语句执行过程如下:

string&& move(string& && t) //此处string& &&等于string&,下文会提及
{
1. return static_cast<typename std::remove_reference<_Tp>::type&&>(__t);
2. return static_cast<typename std::remove_reference<string&>::type&&>(s);//提取出基础类型string
3. return static_cast<string&&>(s)
}

所以,某种意义上来说,std::move(lvalue) 就约等于 static_cast(lvalue),即将左值强制转换为右值。而 std::move 中封装了一个类型提取器 std::remove_reference 来方便使用。

5. std::move 中的引用折叠

  通过上文的内容,可以发现 std::move 中的传参类型为 _Tp&& ,如下:

template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

  那么,在执行过程中,传参s的类型是什么呢?
C/C++学习记录:std::move 源码分析_第5张图片
  由于传参类型为 _Tp&&,那么当传参类型为 string&(即左值) 时,当时的场景则为:

std::string s = "Hello";
std::move(s) => std::move(string& &&)

  此时的类型 string& && 又是什么?此处便涉及到了引用折叠。概念如下,简单来说就是除了右值的&&是右值,其他都是左值。

  • X& &、X&& &、X& && —— 折叠成X&,用于处理左值
  • X&& && —— 折叠成X&&,用于处理右值

  引用折叠的意义就是让参数可以与任何类型的实参匹配,简单说就是右值传进来还是右值,左值传进来还是左值。例如上文传进来的就是左值,最后还是左值;如果传进来的是右值则最终还是右值。另外我粗略看了下 forward 的源码,其实它实现完美转发也是很大程度依赖于引用折叠这个东西。

二、总结

  C++的标准库源码依旧封装的很"繁琐",以及配着贼长的命名。当然 std::move 这个函数还好,涉及的东西不算太多,所以看着还是非常清晰的。之前看智能指针源码才是真的给我看的烦躁无比。
  小结一下,std::move 中只进行了一个类型转换,而各种所谓右值数据迁移基本都是在构造函数中实现的。
  总的来说标准库里的源码写的还是相对很严谨和标准的,很多思路和写法确实能让我学到很多。接下来我准备再去仔细研究一下 forward 的实现和思路。当然 std::move 里那个最关键的 static_cast 我还是没有深入的探索,只是浅尝辄止,可能等未来实力和精力足够再来一探究竟吧。

你可能感兴趣的:(C/C++,C++源码分析,c++,c++11,move,std,源码)