gpt4 book ai didi

c++ - 标准库或编译器在哪里利用 noexcept move 语义( vector 增长除外)?

转载 作者:行者123 更新时间:2023-12-03 14:39:48 24 4
gpt4 key购买 nike

move 操作应该是 noexcept ;首先是直观和合理的语义。第二个参数是运行时性能。来自核心指南,C.66 , "使 move 操作 noexcept":

A throwing move violates most people’s reasonably assumptions. A non-throwing move will be used more efficiently by standard-library and language facilities.


本指南性能部分的典型示例是 std::vector::push_back 的情况或者 friend 需要增加缓冲区。标准在这里需要强大的异常保证,如果这是 noexcept,这只能将元素 move 构造到新缓冲区中。 - 否则,必须复制。我明白了,差异在基准测试中是可见的。
然而,除此之外,我很难找到真实世界的证据来证明 noexcept 对性能的积极影响。 move 语义。浏览标准库( libcxx + grep ),我们看到 std::move_if_noexcept存在,但它几乎不在库本身中使用。同样, std::is_noexcept_swappable仅用于充实条件 noexcept预选赛。这与现有声明不符,例如 Andrist 和 Sehr 来自“C++ High Performance”的声明(第 2 版,第 153 页):

All algorithms use std::swap() and std::move() when moving elements around, but only if the move constructor and move assignment are marked noexcept. Therefore, it is important to have these implemented for heavy objects when using algorithms. If they are not available and exception free, the elements will be copied instead.


把我的问题分成几部分:
  • 标准库中是否有类似 std::vector::push_back 的代码路径? ,当喂食 std::is_nothrow_move_constructible 时运行得更快类型?
  • 我认为书中引用的段落不正确是否正确?
  • 当类型符合 noexcept 时,编译器何时能够可靠地生成运行时效率更高的代码,是否有明显的例子?指南?

  • 我知道第三个可能有点模糊。但如果有人能提出一个简单的例子,那就太好了。

    最佳答案

    背景:我指的是std::vector使用 noexcept 作为“vector 悲观化”。我声称vector悲观是任何人关心放置 noexcept 的唯一原因关键字进入语言。此外,vector悲观化仅适用于元素类型的 move 构造函数。我声称将您的 move 分配或交换操作标记为 noexcept没有“游戏内效果”;撇开它是否在哲学上令人满意或在风格上正确不谈,您不应该期望它对您的代码性能产生任何影响。
    让我们检查一个真正的库实现,看看我离错误有多近。 ;)

  • vector 重新分配。 libc++ 的头文件使用 move_if_noexcept仅限内部__construct_{forward,backward}_with_exception_guarantees ,仅在 vector 重新分配中使用。
  • variant 的赋值运算符.内__assign_alt ,代码标签在 is_nothrow_constructible_v<_Tp, _Arg> || !is_nothrow_move_constructible_v<_Tp> 上调度.当您这样做时myvariant = arg; ,默认的“安全”方法是构造一个临时的 _Tp来自给定的 arg ,然后销毁当前放置的替代方案,然后 move 构造该临时 _Tp进入新的替代方案(希望不会抛出)。但是,如果我们知道 _Tp不能直接从 arg 构造,我们就这样做;或者,如果 _Tp的 move 构造函数正在抛出,因此“安全”方法实际上并不安全,那么它不会为我们购买任何东西,无论如何我们只会执行快速直接构造方法。

  • 顺便说一句, optional 的赋值运算符不做任何这种逻辑。
    请注意,对于 variant赋值,使用 noexcept move 构造函数实际上会损害(未优化)性能,除非您还将选定的转换构造函数标记为 noexcept ! Godbolt.
    (这个实验也在 libstdc++ 中发现了一个明显的错误: #99417 。)
  • string附加/插入/分配。这是一个令人惊讶的。 string::append拨打 __append_forward_unsafe正在接受 SFINAE 检查 __libcpp_string_gets_noexcept_iterator .当您这样做时s1.append(first, last) ,我们想做s1.resize(s1.size() + std::distance(first, last))然后复制到这些新字节中。但是,这在三种情况下不起作用: (1) 如果 first, last指向 s1本身。 (2) 如果 first, last正是input_iterator s(例如从 istream_iterator 中读取),因此已知不可能迭代范围两次。 (3) 如果有可能对范围进行一次迭代可能会将其置于第二次迭代会抛出的错误状态。也就是说,如果第二个循环中的任何操作( ++==* )是非无异常(exception)的。所以在这三种情况中的任何一种情况下,我们都会采取“安全”的方法来构建一个临时 string s2(first, last)然后 s1.append(s2) . Godbolt.

  • 我敢打赌控制这个的逻辑 string::append优化不正确。 ( EDIT: yes, it is. ) 见 "Attribute noexcept_verify " (2018-06-12)。还有 observe in that godbolt对 libc++ 至关重要的操作是 rv == rv ,但它实际上在内部调用的那个 std::distancelv != lv .
    同样的逻辑更适用于 string::assignstring::insert .我们需要在修改字符串的同时迭代范围。所以我们需要要么保证迭代器操作是 noexcept,要么需要一种在抛出异常时“退出”我们的更改的方法。当然还有 assign特别是,没有任何方法可以“取消”我们的更改。在这种情况下唯一的解决方案是将输入范围复制到临时 string 中。然后从中分配 string (因为我们知道 string::iterator 的操作是 noexcept ,所以他们可以使用优化的路径)。
    libc++ 的 string::replace不做这个优化;它总是将输入范围复制到临时 string首先。
  • function SBO。 libc++ 的 function仅当存储可调用对象 is_nothrow_copy_constructible 时才使用其小缓冲区(当然小到可以装下)。在这种情况下,可调用对象被视为一种“仅复制类型”:即使您 move 构造或 move 分配 function ,存储的可调用对象将是复制构造的,而不是 move 构造的。 function甚至根本不需要存储的可调用对象是可 move 构造的!
  • any SBO。 libc++ 的 any仅当存储可调用对象 is_nothrow_move_constructible 时才使用其小缓冲区(当然小到可以装下)。不像 function , any将“move ”和“复制”视为不同的类型删除操作。

  • 顺便说一句,libc++ 的 packaged_task SBO 不关心抛出 move 构造函数。它的 noexcept move 构造函数会很高兴地调用用户定义的可调用对象的 move 构造函数: Godbolt.这导致调用 std::terminate如果可调用的 move 构造函数确实抛出过。 (令人困惑的是,打印到屏幕上的错误消息使它看起来好像异常正在从 main 的顶部逃逸出来;但这实际上并不是内部发生的事情。它只是从 packaged_task(packaged_task&&) noexcept 的顶部逃逸出来并被暂停在那里 noexcept。)

    一些结论:
  • 避免vector悲观,您必须声明您的 move 构造函数 noexcept。我仍然认为这是一个好主意。
  • 如果你声明你的 move 构造函数 noexcept,那么为了避免“variant 悲观化”,你还必须声明你所有的单参数转换构造函数 noexcept。然而,“variant 悲观化”只需要一个 move 构造;它不会一直降级为复制结构。因此,您可能可以安全地吃掉这笔费用。
  • 声明你的复制构造函数 noexcept可以在 libc++ 的 function 中启用小缓冲区优化.但是,这仅适用于 (A) 可调用和 (B) 非常小以及 (C) 没有默认复制构造函数的事物。我认为这描述了空集。别担心。
  • 声明迭代器的操作 noexcept可以在 libc++ 的 string::append 中启用(可疑的)优化.但实际上没有人关心这个;而且,无论如何,优化的逻辑是有问题的。我非常在考虑提交一个补丁来删除那个逻辑,这将使这个要点过时。 (编辑:补丁 submitted ,还有 blogged 。)

  • 我不知道 libc++ 中的其他任何地方都关心 noexceptness。如果我错过了什么,请告诉我!我也很想看到 libstdc++ 和 Microsoft 的类似概要。

    关于c++ - 标准库或编译器在哪里利用 noexcept move 语义( vector 增长除外)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66459162/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com