抛出异常时不调用Move构造函数

时间:2015-09-24 10:48:10

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

我有一个变量,它累积当前异常,并且需要在抛出当前异常时进行清理(以便不会再次报告相同的错误)。问题是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

2 个答案:

答案 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)。