Effective Modern C++ Item 29 的学习和解读。
在 C++11 新增特性中,移动语义无疑是最重要的一个,它允许编译器使用高效的 move *** 作代替低效的 copy *** 作。一般地,把你的 C++98 代码使用 C++11 编译器重新编译后,运行的会更快一些。
然而,凡是都不是绝对的,本 item 会介绍一些移动语义不可用、不那么高效的场景。
对于 C++ 标准库,针对 C++11 特性做了大量的修改,添加了对移动语义的支持,对 C++ 标准库使用移动 *** 作基本上都会带来性能的提升。但对于我们自己存量的老代码,多数是不支持移动语义的。并且在 Item 17 中也介绍了,编译器只会在没有用户自定义拷贝 *** 作和析构函数时才会生成移动 *** 作。因此,这种情况下无法享受到移动语义带来的性能收益。
C++11 标准库已经都支持移动 *** 作了,但不意味着一定都会带来性能的提升。例如 std::array,其实它本质上是披着标准库容器接口外衣的数组。一般的 STL 容器的对象,其数据成员是在堆上,对象中有一个指针指向这个堆。这个指针的存在,让容器内容的移动只要将目标容器的指针指向源容器的堆,然后将源容器的指针设置为空即可。
std::array 没有这样的指针,它的内容直接存储在对象的 buffer 中,它的移动没法像一般的容器那样通过直接改变容器中指针的指向来高效完成移动。std::array 的移动需要将数据一个一个移动或拷贝。
对于 std::string,提供了常量时间的移动和线性时间的拷贝,听起来移动比拷贝高效很多。然而,也有例外。std::string 有一种实现叫 SSO(small string optimization),对于小字符串(例如少于 15 个字符)其数据直接存储在对象中,而不存储在堆上。SSO 直接使用对象内部的 buffer 存放内容,而省去动态申请堆内存。移动基于 SSO 实现的小字符串并不会比拷贝高效。
即使对于支持移动语义的类型,看似一定使用移动的场景,却最终使用的是拷贝。Item 14 介绍了一些标准库 *** 作提供了异常规范影响移动语义的场景。只有移动 *** 作一定不会抛出异常的情况下,拷贝 *** 作在内部才会被移动 *** 作替换。如果移动 *** 作没有被申明为 noexcept,即使是适合移动 *** 作的场景,编译器也会依然生成拷贝 *** 作。
此外,虽然左值可以使用 std::move 将其转换为右值进行移动 *** 作,但会存在一些异常情况,参见 Item 25 。因而尽可能只对右值进行移动 *** 作。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)