- 抽空扣一点感兴趣的标准库源码,这里总结一下
std::move()
相关的分析 - 本文中 gcc version: 8.4.1 20200928 (Red Hat 8.4.1-1) (GCC)
- 其中c++库安装路径为
/usr/include/c++/8
目录
一、源码与分析
1. std::move 源码总览
std::move()
的定义位于 /usr/include/c++/8/bits/move.h
中,详细内容如下:
可以看到 std::move
的定义只有短短4行…
第二行和第三行关键字 constexpr
和 noexcept
都是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
中,详细内容如下:
可以看到,std::remove_reference
结构体的实现非常简单,功能就是依靠模板把传参 _Tp
的类型分离出来,当调用 std::remove_reference::type
时即为分离出的最底层类型。
- 测试如下内容:
结果如下:
说明std::remove_reference
可以很好的将类型提取出来,即int&
和int&&
都可以提取出基础类型int
3. static_cast 分析
static_cast
也是c++11中的新特性,简单来说用处就是类型转换。语法为:
static_cast<新类型>(表达式)
其返回值为 “新类型” 类型的值,例如 n = static_cast<int>(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<T&&>(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的类型是什么呢?
由于传参类型为 _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
我还是没有深入的探索,只是浅尝辄止,可能等未来实力和精力足够再来一探究竟吧。