C++ 投掷可移动物体
我注意到,当抛出的类型是可移动的时,MSVC和g++在处理临时异常对象的创建时有一点不同。追查这些数据引发了更多的问题 在进一步讨论之前,我的问题的核心是:在没有复制/移动省略的情况下,标准中谁能很好地说明如何创建临时异常对象?目前,我能做的最好的事情就是引用15.1/3中的以下内容: throw表达式初始化临时对象,称为异常对象,其类型是通过从throw操作数的静态类型中删除任何顶级cv限定符,并将类型从“T数组”或“函数返回T”调整为“指向T的指针”或“指向函数返回T的指针”来确定的,分别 我猜答案隐藏在定义表达式类型和对象初始化方式的其他语言中,但我没有运气将其拼凑在一起。当抛出一个对象时,异常对象是否得到(a)复制构造,(b)移动构造(如果合适),复制构造(否则),或者(c)以实现定义的方式初始化 考虑以下代码:C++ 投掷可移动物体,c++,exception,c++11,C++,Exception,C++11,我注意到,当抛出的类型是可移动的时,MSVC和g++在处理临时异常对象的创建时有一点不同。追查这些数据引发了更多的问题 在进一步讨论之前,我的问题的核心是:在没有复制/移动省略的情况下,标准中谁能很好地说明如何创建临时异常对象?目前,我能做的最好的事情就是引用15.1/3中的以下内容: throw表达式初始化临时对象,称为异常对象,其类型是通过从throw操作数的静态类型中删除任何顶级cv限定符,并将类型从“T数组”或“函数返回T”调整为“指向T的指针”或“指向函数返回T的指针”来确定的,分别
#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;
}
}
MSVC2010中的相同代码,无论优化设置如何,结果都是相同的,只是两个移动是副本。这就是最初引起我注意的差异
我假设的第一个测试很好;这是经典的复制省略
在第二个测试中,gcc的行为与我预期的一样。临时的Blob
被视为一个xvalue,并从中构造异常对象move。但是我不确定编译器是否需要识别原始的Blob
即将过期;如果不是,那么MSVC在复制时的行为是正确的。因此,我的原始问题是:标准是否要求这里发生什么,或者它只是实现定义的行为的一部分,而不是异常处理的固有部分
第三个测试正好相反:MSVC的行为符合我的直觉要求。gcc选择从b
移动,但是b
仍然是活动的,我在处理抛出的异常后继续使用它就证明了这一点。显然,在这个简单的示例中,移动或复制对b
本身没有影响,但在考虑重载解析时,编译器肯定不允许查看这一点
显然,复制/移动省略的存在使得这个简单的测试很难从中概括出来,但更大的问题是,两个编译器都可能还不是complient[特别是在gcc的第三个测试中,以及一般的MSVC中]
注:这完全是出于学术目的;我几乎从不抛出任何东西,除了一个临时的,不管怎样,这两个编译器都在适当的地方构造了它,而且我非常确定这种行为是允许的 抛出是一种由实现定义的行为。在C++03中,异常被复制了实现定义的次数,放置在依赖于实现的位置,在catch块期间被引用,然后被销毁。在C++0x中,我希望实现要么有权复制和移动它任意多次,要么有权移动它任意多次(即,可以抛出不可复制的类) 但是,您当然不允许访问在捕获过程中从中移动的对象,因为这将非常糟糕™. 如果您已经这样做了,那么这是一个编译器错误。您应该打印对象的地址以确定
您还应该记住的是,MSVC的实现是根据多年前存在的规则进行的,而GCC的rvalues实现则要晚得多。自MSVC实施这些规则以来,这些规则可能已经发生了变化。但是,编译器在尝试抛出不可复制的类时会出错,向我建议编译器可以自由复制和移动异常对象。移动行为符合情况2,但不符合情况3。见第12.8节[类别副本]/p31: 当满足某些标准时,一个 允许实现省略 复制/移动类的构造 对象,即使复制/移动 函数的构造函数和/或析构函数 对象有副作用
- 在抛出表达式中,当操作数是非易失性 自动对象(非自动对象) 函数或catch子句参数) 其范围不超出 最内层封闭结构的末端 try block(如果有),则 从操作数复制/移动操作 异常对象(15.1)的 通过构造自动 对象直接导入异常 反对
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}