【问题标题】:Does an exception use move semantics when thrown in C++11?在 C++11 中抛出异常时是否使用移动语义?
【发布时间】:2012-08-20 18:43:45
【问题描述】:

http://www.drdobbs.com/cpp/practical-c-error-handling-in-hybrid-env/197003350?pgno=4

在本文中,Herb Sutter 解释说,抛出异常需要异常的副本,因为它是作为临时创建的,因此使用std::auto_ptr 来绕过复制开销。鉴于 C++11 中提供了移动语义,这还有必要吗?

【问题讨论】:

  • 这个副本通常会被一个体面的编译器优化掉。您可以通过在复制构造函数上设置调试器断点来检查您的。
  • 异常性能不应该是您关心的问题。如果是,请更正您的程序,使其不是。
  • 同意,这更具学术性。
  • 它确实允许投掷只移动类型。如果它需要可复制性,例如,您不能抛出 std::unique_ptr
  • 至少,MSVC++2013 不会为throw std::move(ex); 调用移动构造函数:stackoverflow.com/questions/32759433/…

标签: c++ c++11 move-semantics


【解决方案1】:

我刚刚检查过,标准允许

  • 省略将 throw 表达式的操作数指定的对象复制或移动到异常对象中
  • 如果您不更改程序的含义(即如果您重新抛出并且随后的捕获会突然看到一个已更改的异常对象被前一个 catch 块更改)。

由于允许这些省略,规范要求首先将复制或移动的来源视为右值。所以这意味着如果可能的话,相应的对象被移动。当然,复制和移动省略仍然可以作为首选。


更新

我被告知,将 catch 子句参数的异常对象初始化程序作为右值初始化程序的考虑可能会从标准中删除(因为通常不可能在所有情况下检测程序的行为何时是省略复制/移动时保持不变),所以我建议不要依赖这个(上面的第二个项目符号)。

可以仍然依赖的是将局部变量移动到异常对象中,如throw x;(上面的第一个项目符号)。

【讨论】:

  • 当您说“各个对象被移动”时,您的真正意思是必须还是可以?我的apparently duplicate question 演示了 Visual C++ 2012 不会移动对象,而是调用复制构造函数。这是符合标准的缺陷吗?
  • @Carsten:根据我的理解,我倾向于认为这是一个错误。
【解决方案2】:

从异常对象移动现在不是强制性的。

这是 C++11 的一个缺陷。见CWG1493

【讨论】:

    【解决方案3】:
    • 在抛出表达式时,始终需要创建异常对象的副本,因为原始对象在堆栈展开过程中超出范围。
    • 在初始化期间,我们可能会期望复制省略(参见此内容)——省略复制或移动构造函数(对象直接构造到目标对象的存储中)。
    • 但是,即使可能应用或不应用复制省略,您也应该提供正确的复制构造函数和/或移动构造函数,这是 C++ 标准要求的(参见 15.1)。请参阅下面的编译错误以供参考。

    但现代 C++ 提供了更多功能:“使用移动语义和异常安全移动”

    struct demo
    {
        demo() = default;
        demo(const demo &) { cout << "Copying\n"; }
        // Exception safe move constructor
        demo(demo &&) noexcept { cout << "Moving\n"; }
    private:
        std::vector<int>    m_v;
    };
    int main()
    {
        demo obj1;
        if (noexcept(demo(std::declval<demo>()))){  // if moving safe
            demo obj2(std::move(obj1));             // then move it
        }
        else{
            demo obj2(obj1);                        // otherwise copy it
        }
        demo obj3(std::move_if_noexcept(obj1));     // Alternatively you can do this----------------
        return 0;
    }
    
    • 我们可以使用noexcept(T(std::declval&lt;T&gt;()))来检查T的移动构造函数是否存在并且是noexcept,以决定我们是否要通过移动T的另一个实例来创建T的实例(使用std::move)。
    • 或者,我们可以使用std::move_if_noexcept,它使用noexcept 运算符并强制转换为右值或左值。此类检查用于std::vector 和其他容器。
    • 这在您处理不想丢失的关键数据时很有用。例如,我们有从服务器接收到的关键数据,我们不想在处理过程中不惜一切代价丢失它。在这种情况下,我们应该使用std::move_if_noexcept,它只会移动关键数据的所有权并且只有移动构造函数是异常安全的。

    发件人:7 best practices for exception handling in C++

    【讨论】:

      猜你喜欢
      • 2011-10-11
      • 2011-06-11
      • 2012-01-15
      • 1970-01-01
      • 1970-01-01
      • 2017-07-06
      • 2019-01-02
      • 2013-09-04
      相关资源
      最近更新 更多