我可以抛出unique_ptr吗?

Ron*_*urk 3 c++ stl exception-handling unique-ptr

我已经开始使用C++ 11,特别是使用unique_ptr来使代码异常安全且所有权更具可读性.这通常很有效,直到我想抛出unique_ptr.我有错误代码(抛出许多地方,陷入一个地方),创建复杂的状态.由于逻辑上动态分配的内存的所有权正从thrower传递到catcher,所以unique_ptr似乎是适当的类型来表示并明确catcher已经获得了一个堆对象.没有工作,至少使用免费的Visual Studio 2013.这是一个简化的代码示例,它不再像任何有用的东西,但会引发行为:

// cl /nologo /EHsc /W4 test1.cpp
#include <memory>
using std::unique_ptr;
class IError
    {
public:
    virtual ~IError() {};
    virtual void DoStuff();
    };

unique_ptr<IError> Error();

int Foo() { throw Error(); }

int main(void)
    {
    try {
        Foo();
        }
    catch(unique_ptr<IError> Report)
        {
        Report->DoStuff();
        }
    return 0;
    }
Run Code Online (Sandbox Code Playgroud)

并且编译器如此喷出:

test1.cpp
test1.cpp(13) : warning C4673: throwing 'std::unique_ptr<IError,std::default_delete<_Ty>>' the following types will n
ot be considered at the catch site
        with
        [
            _Ty=IError
        ]
test1.cpp(13) : warning C4670: '_Unique_ptr_base<class IError,struct std::default_delete<class IError>,1>' : this bas
e class is inaccessible
test1.cpp(13) : error C2280: 'std::unique_ptr<IError,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,
std::default_delete<_Ty>> &)' : attempting to reference a deleted function
        with
        [
            _Ty=IError
        ]
        C:\bin\Visual Studio Express 2013\VC\INCLUDE\memory(1486) : see declaration of 'std::unique_ptr<IError,std::d
efault_delete<_Ty>>::unique_ptr'
        with
        [
            _Ty=IError
        ]
Run Code Online (Sandbox Code Playgroud)

我哪里做错了?

小智 5

作为替代我将使用Rextester具有MSVC版本18.00.21005.1.对于GCC 4.8.1和Clang 3.5,我将使用Coliru.现在,最初在给出一个仓促的答案时,我说unique_ptrs不能被复制,所以你应该通过引用来捕捉它们.但是,当您在MSVC中抛出对象时,似乎会发生错误.所以上述建议仅适用于GCC和Clang.

catch(unique_ptr<IError>& Report)
Run Code Online (Sandbox Code Playgroud)

似乎他们在MSVC如何处理复制/移动省略和/或移动语义方面存在差异,我在C++方面的表现还不够明确,但让我们展示一些可编译的例子.首先是一个带有删除的复制构造函数的基本结构:

#include <iostream>
struct D {
    D() {};
    D(const D& other) = delete;
    D(D&& other) { std::cout << "call D move constructor... \n"; }
};

int main()
{
    try {
        throw D();
    } catch(D const& d)
    {   
    }
}
Run Code Online (Sandbox Code Playgroud)

无论优化级别如何,对于GCC和Clang都没有输出,除非你还添加-fno-elide-constructors到调用中,我们看到它们都调用了移动构造函数.对于MSVC,我们收到此错误:

source_file.cpp(22) : error C2280: 'D::D(const D &)' : attempting to reference a deleted function
        source_file.cpp(7) : see declaration of 'D::D'
Run Code Online (Sandbox Code Playgroud)

有关更复杂的示例,请参阅投掷可移动对象.问题是两年了但我们观察到相同的行为,GCC和Clang在某些情况下都调用移动构造函数,但MSVC在所有情况下都调用复制构造函数(GCC和Clang在该Throw with object not about to die anyhow (enter non-zero integer部分不同).

Throw directly: 
C
caught: 007FFA7C
~
Throw with object about to die anyhow
C
c
~
caught: 007FFA74
~
Throw with object not about to die anyhow (enter non-zero integer)
C
c
caught: 007FFA70
~
1
~
Run Code Online (Sandbox Code Playgroud)

TL; DR GCC和Clang将编译它,但MSVC不会.一个糟糕的解决方法是抛出一个指针:

throw new unique_ptr<IError>;

catch(unique_ptr<IError>* Report);
Run Code Online (Sandbox Code Playgroud)

  • @Ron也许这是相关的:[投掷可移动物体](/sf/ask/444670691/).尝试自己测试一下.删除Blob的复制构造函数.gcc和clang都调用移动构造函数,Visual Studio会出错,因为它调用了复制构造函数.请注意,我使用的是[Rextester](http://rextester.com/),其版本为18.00.21005.1,因此可能是VS2013. (3认同)