我有一个变量,它累积当前异常,并且需要在抛出当前异常时进行清理(以便不会再次报告相同的错误)。问题是throw std::move(ex);
不调用移动构造函数(它会清除ex
),而是调用复制构造函数(这样ex
也会停留已经抛出的错误)。 MVCE如下:
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class ThrowMoveTest : exception
{
public:
ThrowMoveTest(const string& what)
{
_what = what;
}
ThrowMoveTest(const ThrowMoveTest& fellow)
{
cout << "Copy " << what() << endl;
_what = fellow._what;
}
ThrowMoveTest(ThrowMoveTest&& fellow)
{
cout << "Move " << what() << endl;
_what = std::move(fellow._what);
}
virtual const char* what() const override
{
return _what.c_str();
}
private:
mutable string _what;
};
int main()
{
try
{
ThrowMoveTest tmt1("Test1");
throw move(tmt1);
}
catch (const ThrowMoveTest& ex)
{
cout << "Caught " << ex.what() << endl;
}
return 0;
}
我正在使用MSVC ++ 2013 Update 5。
是否存在我做错的事情,因此移动构造函数不会被调用?有没有抛出异常,以便在C ++中用于异常存储的临时对象是移动构造的,而不是从原始构造复制构造?
我试图避免的是双重复制:构建tmt1
的副本,然后清理原始文件,然后使用throw
语句中的副本,这将构建另一个临时存储副本。
编辑:上面的代码示例在MSVC ++ 2013 Update 5上提供了以下输出
Copy
Caught Test1
预期输出为
Move
Caught Test1
EDIT2:提交编译器错误报告https://connect.microsoft.com/VisualStudio/feedback/details/1829824
答案 0 :(得分:3)
这是一个MSVC错误。来自[except.throw]:
抛出异常copy-initializes(8.5,12.8)一个名为异常对象的临时对象。
这意味着我们这样做:
ThrowMoveTest __exception_object = move(tmt1);
绝对应该调用移动构造函数。
请注意,这里的move
是不必要的,也是有害的。 [class.copy]规定可以省略复制/移动构造
- 在 throw-expression (5.17)中,当操作数是非易失性自动对象的名称时(除了 函数或catch子句参数),其范围不会超出最内层的末尾 封闭try-block(如果有的话),从操作数到异常的复制/移动操作 通过将自动对象直接构造到异常对象
中,可以省略object(15.1)
因此,throw tmt1;
只允许将tmt1
直接构造到异常对象中。虽然gcc和clang都不这样做。
即使没有删除复制/移动:
当满足复制/移动操作的省略标准时,但不符合例外声明,并且 要复制的对象由左值[...]重载决策指定 首先执行选择复制的构造函数,就好像该对象是由右值指定的。
所以throw tmt1;
仍会移动 - 构造异常对象。
答案 1 :(得分:1)
这是编译器错误。标准参考§12.8/ p32声明它应该调用移动构造函数(确认@Piotr Skotnicki)。