我能扔掉unique_ptr吗?

3

我开始使用C++11,特别是广泛地使用unique_ptr来使代码具有异常安全性和更易读的所有权。这通常效果很好,直到我想抛出一个unique_ptr。我有一个错误码(在许多地方抛出,在一个地方捕获),它创建了复杂的状态。由于动态分配的内存的所有权逻辑上从抛出者转移到了接收者,因此unique_ptr似乎是指示这一点并清楚地表明接收者已经获取堆对象的合适类型。但是在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;
    }

编译器会输出以下信息:

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
        ]

我哪里做错了?


异常处理符合RAII,你为什么还有担忧呢?正如Syl所说,抛出时按值传递,捕获时按引用传递。 - Sebastian Hoffmann
3个回答

4
作为替代品,我将使用MSVC版本为18.00.21005.1的Rextester。对于GCC 4.8.1和Clang 3.5,我将使用Coliru。一开始匆忙回答时,我说unique_ptr不能被复制,所以您应该通过引用来捕获它们。然而,当您在MSVC中抛出对象时,错误会出现。因此上述建议仅适用于GCC和Clang。
catch(unique_ptr<IError>& Report)

看起来它们在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)
    {   
    }
}

无论是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'

对于一个更加复杂的例子,请参见抛出可移动对象。虽然这个问题已经两年了,但我们在GCC和Clang中观察到相同的行为,它们在某些情况下调用移动构造函数,但MSVC在所有情况下都调用拷贝构造函数(GCC和Clang在抛出一个不会死亡的对象(输入非零整数)部分有所不同)。

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
~

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

throw new unique_ptr<IError>;

catch(unique_ptr<IError>* Report);

3
也许这个与之相关:抛出可移动对象。尝试自行测试。将 Blob 的复制构造函数删除。GCC 和 Clang 调用移动构造函数,Visual Studio 会出错,因为它调用了复制构造函数。请注意,我正在使用版本 18.00.21005.1 的 Rextester,因此可能是 VS2013。 - user1508519
@remyabel 我总是忘记C++11的移动能力改变了一切。谢谢你提供的链接。 - Mark Ransom
@Mark 我觉得这是移动语义和省略的组合。看一下这个Coliru上的小例子:它没有输出任何东西,这表明既没有调用复制构造函数也没有调用移动构造函数。然而,在 VC++ 中它失败了。我猜我们可以使用“-fno-elide-constructors”来进一步研究这个问题。 - user1508519
1
@remyabel 在链接问题的Howard Hinnant的被接受答案中提到:"重载决议需要进行两次处理"。我猜想这是因为VS以其缺乏两次处理重载决议而臭名昭著;这似乎是又一个例子。 - Ali
谢谢。我不得不去设置一个最新的g++环境来尝试你的答案,我相信它是正确的。如果我可以避免使用vc++,我会选择unique_ptr&,但我想我必须使用原始指针。我想我可以让所有的throwers传递unique_ptr.release(),然后让单个catcher立即将原始指针放回unique_ptr中。这仍然使代码中的所有权转移更或多或少地自我记录。 - Ron Burk
显示剩余5条评论

3

在处理异常时,一个常见的规则是“按值抛出,按const引用捕获”。我猜你不能复制unique_ptr,这可能是问题的一部分。

将您的类Error从std :: exception派生,并抛出该异常。


0

你的错误是由于 std::unique_ptr (故意地)没有复制构造函数,而编译器在捕获异常时尝试调用它。

请注意,在你的 catch 子句中,任何编译器都不能尝试移动 std::unique 对象(不能从异常对象中移动)。

一方面,

你可以通过引用来捕获你的异常 - 这样就可以避免在 catch 子句中进行复制。因此,这个特定的错误将会消失。

请注意, 如果你通过值来捕获异常,(自 C++11 以来)编译器可能会在 catch 子句中执行复制省略,如果复制省略不会因为跳过 catch 子句参数的复制构造函数和析构函数而改变程序的可观察行为(例如,如果 catch 子句参数被修改,并且异常对象被重新抛出)。虽然这是一个非强制性的省略,但由于你的编译器不执行它并不违反语言标准。

另一方面,

用于抛出异常的类型必须具有复制构造函数。 除了在仅允许但不强制执行抛出和捕获时进行复制省略之外,还有一些情况下复制省略是不明智的。 即使在使用复制省略或在抛出异常时进行移动的情况下,在catch子句中事实上避免复制,对于用于异常的类型(类)而言,没有复制构造函数是不正确的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接