条款29.假定移动操作不存在,成本高,未使用

编程入门 行业动态 更新时间:2024-10-19 14:51:03

条款29.假定移动操作<a href=https://www.elefans.com/category/jswz/34/1770716.html style=不存在,成本高,未使用"/>

条款29.假定移动操作不存在,成本高,未使用

假定移动操作不存在,成本高,未使用

让我们从为何许多类型不能支持移动语义的观察开始。整个C++98标准库都已为C++11彻底翻新过,为很多类型提供了移动的能力,这些类型的移动实现比复制操作更快,并且对库的组件实现修改以利用移动操作。不过问题在于你有可能受伤的代码并未完成修订以充分利用C++11的良好特性。若你的应用中的类型没有为C++11做过专门修改,那么仅仅在编译器中有着对移动操作的支持也并不会给你带来什么明显好处。诚然,C++11愿意为这些缺少移动操作的类创建移动操作,但这也仅适用于那些未声明拷贝操作,移动操作以及析构函数的类。类型若是有数据成员或是基类禁用了移动,也将导致编译器生成的移动操作被抑制掉。如果类型并不提供对移动的显式支持,也不符合编译器生成移动操作的条件,当然也就没有理由期望它在C++11下比C++98下有任何性能提升

即使对于那些显式支持移动操作的类型,也可能不会像人们期望的那样带来巨大的效率。举个例子,在标准的C++11库中,所有的容器都支持移动操作,但如果因此就断言所有的容器的移动都是成本廉价的,那就贻笑大方了。对于有些容器来说,根本就没有什么成本低廉的途径来移动其内容。而对于另一些容器而言,它们虽然有着确实成本低廉的移动操作,但却又有一些附加条件造成其容器元素不能满足。

考虑以下std::array这个C++11中引入的新容器类型,它实质上就是带有STL接口的内建数组。这一点和其他标准容器存在根本差异。因为其他标准容器都是将其内容存放在堆上的,从而在概念上,只需(以数据成员的方式)持有一个指向存放容器内容的堆内存的指针。由于该指针的存在,把整个容器的内容在常数时间内加以移动就成为了可能:仅仅把那个指向容器内容的指针从原容器复制到目标容器,然后把原容器包含的指针置空即可。

std::vector<Widget> vw1;//将数据放入vw1...//移动vw1到vw2
//完成执行仅须常数时间
//仅仅是包含在vw1和vw2的指针被修改了
auto vw2 = std::move(vw1);

std::array类型的对象则缺少这样一个指针,因为其内容数据是直接存储在对象内的。

std::array<Widget, 10000> aw1;//将数据放入aw1...//移动aw1入aw2
//完成执行需要线性时间
//需要把aw1中的所有元素移动入aw2
auto aw2 = std::move(aw1);

请注意,aw1中的元素是被移动入aw2中的,假定Widget类型的移动比复制快,则移动一个元素类型为Widgetstd::array自然也会比复制一个同样的std::array更快。毫无疑问,std::array当然提供移动支持。然而无论是移动还是复制std::array类型的对象都还是需要线性时间的计算复杂度,因为容器中的每个元素都必须逐一复制或移动。

作为对比,std::string类型提供的是常数时间的移动和线性时间的复制。这听起来像是再说,它的移动比复制更快,但可能并非如此。许多string的实现都采用了小型字符串优化(SSO)。采用了SSO之后,小型字符串会存储在std::string对象内的某个缓冲区内,而不去使用在堆上分配的存储。在使用了基于SSO的实现的前提下,对小型字符串实施移动并不比复制更快,因为通常在移动的性能优于复制背后“仅复制一个指针”的把戏会失灵。

SSO的动机,就充分表明短字符串才是大量应用使用的习惯。使用一个内部缓冲区而不分配内存空间,是为了更好的效率。然而这种内存管理的效率导致移动的效率并不比拷贝操作高。

即使对于支持快速移动操作的类型,一些看似万无一失的移动场景还是以复制副本告终。条款14解释过,标准库中一些容器操作提供了强异常安全保证,并且为了确保依赖于这样的保证,那些C++98的遗留代码在升级到C++11时不会破坏这样的保证,底层的拷贝操作只有在已知移动操作不会抛出异常的前提下才会使用移动操作将其替换这么做导致的一个后果是,即使某个类型的移动操作比对应的拷贝操作更高效,甚至在代码的某个特定位置,移动操作一般肯定不会有问题,编译器仍会强制去调用一个复制操作,只要对应的移动操作未加上noexcept声明

总而言之,在这个几个场景中,C++11的移动语义不会带给你什么好处

  • 没有移动操作:待移动的对象未能提供移动操作。因此,移动请求就变成了复制请求
  • 移动未能更快:待移动的对象虽然有移动操作,但并不比其复制操作更快
  • 移动不可用:移动本可以发生的语境下,要求移动操作不可发射异常,但该操作未能加上noexcept声明。

值得一提的是,还有另一种使用场景使得移动语义无法提供效率增益。

源对象是个左值:除了极少数例外,只有右值可以作为移动操作的源

本条款的标题是假定移动操作不存在,成本高,未使用。这比较典型地适用于通用代码的情形。例如,撰写模板的时候,因为你还不知道将要与哪些类型配合。在这种情况下,你必须要像在C++98中一样保守地去复制对象,正如移动语义尚不存在那样,这也同样符合“不稳定”代码的情形,即代码中所涉及类型的特征会比较频繁地加以修改

然而,通常你已知代码中会使用的类型,也可以肯定它们的特性不会改变(例如,它们是否支持成本低廉的移动操作)。如果是这样的情形,你就不需要前面那些假定。你可以直接查阅所使用的类型对移动的支持细节。如果涉及的类型能够提供成本低廉的移动操作,并且是在这些移动操作会被调用的语境中使用对象,则可以放心大胆地依靠移动语义来将复制操作替换成相对不那么昂贵的对应移动操作。

要点速记

  • 假定移动操作不存在,成本高,未使用
  • 对于那些类型或对于移动语义的支持情况已知的代码,则无需作以上假定

更多推荐

条款29.假定移动操作不存在,成本高,未使用

本文发布于:2024-02-26 21:24:43,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1703966.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:不存在   假定   条款   成本   操作

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!