c++ - throw 可移动物体

标签 c++ exception c++11

我注意到当抛出的类型是可移动的时,MSVC 和 g++ 如何处理临时异常对象的创建略有不同。追捕这些提出了额外的问题。

在继续之前,这里是我的问题的核心:在没有复制/移动省略的情况下,标准谁很好地说明了应该如何创建临时异常对象?目前,我能做的最好的就是以下引用,来自 15.1/3:

A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively.

我猜答案隐藏在其他地方的语言中,它定义了表达式的类型以及对象的初始化方式,但我没有运气将它们拼凑在一起。当一个对象被抛出时,异常对象是(a)构造拷贝,(b)适当时构造移动,否则构造拷贝,还是(c)以实现定义的方式初始化?

考虑以下代码:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

struct Blob {
  Blob() { cout << "C" << endl; }
  Blob(const Blob&) { cout << "c" << endl; }
  Blob(Blob&&) { cout << "m" << endl; }
  Blob& operator =(const Blob&) { cout << "=" << endl; return *this; }
  Blob& operator =(Blob&&) { cout << "=m" << endl; return *this; }
  ~Blob() { cout << "~" << endl; }

  int i;
};

int main() {
  try {
     cout << "Throw directly: " << endl;
     throw Blob();
  } catch(const Blob& e) { cout << "caught: " << &e << endl; }
  try {
     cout << "Throw with object about to die anyhow" << endl;
     Blob b;
     throw b;
  } catch(const Blob& e) { cout << "caught: " << &e << endl;  }
  {
    cout << "Throw with object not about to die anyhow (enter non-zero integer)" << endl;
    Blob b;
    int tmp;
    cin >> tmp; //Just trying to keep optimizers from removing dead code
    try {
      if(tmp) throw b;
      cout << "Test is worthless if you enter '0' silly" << endl;
    } catch(const Blob& e) { cout << "caught: " << &e << endl;  }
    b.i = tmp;
    cout << b.i << endl;
  }
}

这一切都是在 ideone 上重新创建的.正如您[希望] 看到的,gcc via ideone 在第一种情况下创建 Blob 对象,并在后两种情况下移动。结果总结如下,指针值替换为标识符。

Throw directly: 
C {A}
caught: {A}
~ {A}
Throw with object about to die anyhow
C {A}
m {B} <- {A}
~ {A}
caught: {B}
~ {B}
Throw with object not about to die anyhow (enter non-zero integer)
C {A}
m {B} <- {A}
caught: {B}
~ {B}
2
~ {A}

MSVC2010中相同的代码,无论优化设置如何,结果都是一样的,只是两个 Action 是拷贝。这是最初引起我注意的差异。

我假设的第一个测试很好;其经典的复制省略。

在第二个测试中,gcc 的行为符合我的预期。临时 Blob 被视为一个 xvalue,并且异常对象是从它构造的。但我不确定编译器是否需要识别原始 Blob 即将到期;如果不是,则 MSVC 在复制时 Action 正确。因此,我最初的问题是:标准是否规定了这里发生的事情,或者它只是异常处理所固有的实现定义行为的一部分?

第三个测试正好相反:MSVC 的行为符合我的直觉要求。 gcc 选择从 b 移动,但 b 仍然存在,这一点可以从我在处理抛出的异常后继续使用它的事实证明。显然,在这个简单的例子中,移动或复制对 b 本身没有任何影响,但在考虑重载解析时肯定不允许编译器查看这一点。

显然,复制/移动省略的存在使得这个简单的测试难以概括,但更大的问题是,任何一个编译器都可能还不兼容[特别是在 gcc 的第三个测试和 MSVC 的情况下] .

请注意,这完全是出于学术目的;我几乎从不抛出任何东西,除了一个临时的,两个编译器无论如何都在适当的位置构建,我很确定这种行为是允许的。

最佳答案

移动行为符合案例 2,但不符合案例 3。参见 12.8 [class.copy]/p31:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. ...

...

  • in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object

上面没有定义什么时候可以隐式移动对象。但它确实定义了复制/移动省略何时合法。要了解隐式移动何时合法,您必须转到同一部分的第 32 段:

32 When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution ...

本段说明当复制/移动省略是合法的时,重载解决会发生两次:

  1. 首先假设左值是右值,以决定将调用或删除哪个构造函数。

  2. 如果 1) 失败,则将参数作为左值重复重载解析。

这会产生从最好到最坏的移动语义层次结构:

  1. 如果您可以省略构造,请这样做。
  2. 如果您可以将对象移出,请执行此操作。
  3. 否则,如果您可以将对象复制出来,请这样做。
  4. 否则会发出诊断信息。

请注意,对于本地堆栈对象的普通返回,这些规则基本相同。

关于c++ - throw 可移动物体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6352438/

相关文章:

c++ - 使用 SFINAE 时如何避免触发 static_assert?

c++ - 如何协调跨进程的端口使用?

c++ - 在异常对象上调用 std::move 是否正确?

java - 可以将异常存储在变量中以便稍后抛出吗?

java - Java、C++、Python 中的异常模型

c++ - 在 C++ 中创建未初始化的 std::thread 时出现错误 C2280

c++ - 是否可以在nodejs或golang中使用Visual C++ MFC函数插件?

c++ - 取消引用返回的引用

c++ - 输入验证方法?

c++ - 表达式f()>g()的值,当f&g修改同一个全局变量undefined或unspecified时?