【问题标题】:Move constructor is not called when throwing an exception抛出异常时不调用移动构造函数
【发布时间】:2015-09-24 10:48:09
【问题描述】:

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

【问题讨论】:

  • 在 Clang 上它会打印“Move”然后是“Caught Test1”。这是您期望的行为吗?顺便说一句,Clang 让我将 noexcept 添加到 what() 覆盖。
  • @JohnZwinck,是的,这是预期的输出,谢谢!所以问题似乎在于 MSVC++2013 不符合标准,对吧?

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


【解决方案1】:

这是一个 MSVC 错误。来自[except.throw]:

抛出异常会复制初始化(8.5、12.8)一个临时对象,称为异常对象

这意味着我们这样做:

ThrowMoveTest __exception_object = move(tmt1);

这绝对应该调用移动构造函数。


请注意,这里的move 是不必要的,也是有害的。 [class.copy] 规定可以省略复制/移动构造

— 在 throw-expression (5.17) 中,当操作数是非易失性自动对象的名称时(除了 一个函数或 catch 子句参数),其范围不超出最内层的末尾 封闭的try-block(如果有的话),从操作数到异常的复制/移动操作 对象(15.1)可以通过将自动对象直接构造到异常对象中来省略

所以throw tmt1; 将允许将tmt1 直接构造到异常对象中。尽管 gcc 和 clang 都没有这样做。

即使没有省略复制/移动:

当满足省略复制/移动操作的条件,但不满足异常声明时,并且 要复制的对象由左值 [...] 重载决议指定 首先执行为复制选择构造函数,就好像对象是由右值指定的一样。

所以throw tmt1; 仍然会移动构造异常对象。

【讨论】:

    【解决方案2】:

    这是一个编译器错误。标准参考 §12.8/p32 声明它应该调用移动构造函数(确认@Piotr Skotnicki)。

    【讨论】:

    • 12.8/32 和这个有什么关系?你能附上相关的报价吗?
    • 我看不到任何提及这是一个尚未实现的 C++11 功能 on MSDN;认为这是一个错误似乎是合理的。
    猜你喜欢
    • 2012-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-04
    • 2019-07-11
    • 1970-01-01
    • 2014-11-03
    • 1970-01-01
    相关资源
    最近更新 更多