抛出异常时不调用移动构造函数
Move constructor is not called when throwing an exception
我有一个变量,它累积当前异常并且需要在抛出当前异常时进行清理(这样就不会再次报告相同的错误)。问题是 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 更新 5。
是不是我做错了什么导致移动构造函数没有被调用?有没有办法抛出异常,使得C++中用于异常存储的临时对象是移动构造的,而不是从原始对象复制构造的?
我尽量避免的是双重复制:构建tmt1
的副本,然后清理原始副本,然后在throw
语句中使用副本,这将构造另一个临时副本存储。
编辑:上面的代码示例在 MSVC++2013 Update 5 上给出了以下输出
Copy
Caught Test1
而预期的输出是
Move
Caught Test1
EDIT2:提交编译器错误报告https://connect.microsoft.com/VisualStudio/feedback/details/1829824
这是一个编译器错误。标准参考 §12.8/p32 指出它应该调用移动构造函数(确认@Piotr Skotnicki)。
这是一个 MSVC 错误。来自 [except.throw]:
Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object.
这意味着我们这样做:
ThrowMoveTest __exception_object = move(tmt1);
绝对应该调用移动构造函数。
请注意,这里的 move
是不必要的,而且也是有害的。 [class.copy]规定copy/move构造可以省略
— in a throw-expression (5.17), 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
所以简单地 throw tmt1;
将允许 tmt1
直接构造到异常对象中。虽然 gcc 和 clang 都没有这样做。
即使 copy/move 没有被省略:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the
object to be copied is designated by an lvalue [...] overload resolution
to select the constructor for the copy is first performed as if the object were designated by an rvalue.
所以throw tmt1;
仍然会移动构造异常对象。
我有一个变量,它累积当前异常并且需要在抛出当前异常时进行清理(这样就不会再次报告相同的错误)。问题是 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 更新 5。
是不是我做错了什么导致移动构造函数没有被调用?有没有办法抛出异常,使得C++中用于异常存储的临时对象是移动构造的,而不是从原始对象复制构造的?
我尽量避免的是双重复制:构建tmt1
的副本,然后清理原始副本,然后在throw
语句中使用副本,这将构造另一个临时副本存储。
编辑:上面的代码示例在 MSVC++2013 Update 5 上给出了以下输出
Copy
Caught Test1
而预期的输出是
Move
Caught Test1
EDIT2:提交编译器错误报告https://connect.microsoft.com/VisualStudio/feedback/details/1829824
这是一个编译器错误。标准参考 §12.8/p32 指出它应该调用移动构造函数(确认@Piotr Skotnicki)。
这是一个 MSVC 错误。来自 [except.throw]:
Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object.
这意味着我们这样做:
ThrowMoveTest __exception_object = move(tmt1);
绝对应该调用移动构造函数。
请注意,这里的 move
是不必要的,而且也是有害的。 [class.copy]规定copy/move构造可以省略
— in a throw-expression (5.17), 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
所以简单地 throw tmt1;
将允许 tmt1
直接构造到异常对象中。虽然 gcc 和 clang 都没有这样做。
即使 copy/move 没有被省略:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue [...] overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
所以throw tmt1;
仍然会移动构造异常对象。