【问题标题】:Some std::unique_ptr uses and "gotchas"一些 std::unique_ptr 使用和“陷阱”
【发布时间】:2013-07-30 11:34:26
【问题描述】:

C++11 std::unique_ptr 有哪些用途和陷阱

我也可以使用std::unique_ptr 来存储动态分配的数组吗?

我可以将std::unique_ptr 也用于使用自定义删除机制的资源吗?

【问题讨论】:

    标签: c++ c++11 smart-pointers unique-ptr resource-management


    【解决方案1】:

    让我们以问答的形式整理一些用途和陷阱。


    Q1:我想在我的类X 中存储一个指向类Component指针
    我不希望“容器”类X 是可复制的; XComponent 实例的唯一所有者。
    我知道 拥有 raw 指针是一件坏事,并且“泄漏”的潜在来源(而不是 观察 原始指针很好)。 我可以为此使用什么智能指针

    A1: C++11 的 std::unique_ptr 无疑是一个不错的选择。

    唯一(非共享)所有权的情况下很好,并且没有std::shared_ptr的开销。
    它是以前 C++98/03 boost::scoped_ptr 的绝佳替代品。
    事实上,除此之外,std::unique_ptr 还提供了移动语义
    因此,如果类 X 包含 unique_ptr<Component> 数据成员(和其他可移动数据成员),则整个类 X自动移动。

    下面是一个使用示例:

    #include <memory> // for std::unique_ptr
    
    class X
    {
        std::unique_ptr<Component> m_pComponent;
        ....
    
    public:
        X()
            : m_pComponent( new Component() )
        {
            ....
        }
    }
    

    (当然,作为一个智能指针,没有必要在包含的类析构函数中显式删除它。)


    Q2:太好了!无需显式析构函数,无需 std::shared_ptr 开销(典型的 C++ 哲学:“我们不为不使用的东西付费”),移动已经实现的语义机制!
    但是,我有一个问题:我的类Component 有一个构造函数重载,它需要在创建Component 实例之前在构造函数代码中计算一些参数。我尝试在构造函数中使用普通的operator= 赋值将新创建的Component 分配给unique_ptr,但我收到一条错误消息:

    X::X()
    {
        ....
    
        const int param = CalculateCoolParameter();
    
        // This assignment fails:
        m_pComponent = new Component(param); // <---- Error pointing to '=' here
                     ^--- error
    }
    

    A2: 好的,您可能已经预料到operator= 重载会释放以前拥有的 指针(如果有)并分配给新创建的指针。
    不幸的是,没有这样的过载。
    但是,std::unique_ptr::reset() 方法可以!

    m_pComponent.reset( new Component(param) );
    

    Q3:嘿!这个unique_ptr 真的很酷! 我喜欢它的智能、可自动移动且不会带来开销的事实。
    所以,我想用它来存储一个 动态分配的数组 一些恒定大小(在运行时计算)而不是使用 std::vector (在这部分代码中我受到高度限制而且我不想支付std:vector 开销,因为我不想要所有std::vector 动态调整大小功能、深拷贝等)。

    我尝试过这样的事情:

    const size_t count = GetComponentsCount();
    unique_ptr<Component> components( new Component[count] );
    

    它编译得很好,但我注意到~Component 析构函数只被调用一次,而不是我期望count 析构函数调用!这里出了什么问题?

    A3: 问题在于,在上面的语法中,std::unique_ptr 使用delete 来释放分配的对象。但由于这些是使用new[] 分配的,因此正确的清理调用是delete[](不是简单的不带括号的delete)。

    要解决这个问题并指示unique_ptr 正确使用delete[] 来释放资源,必须使用以下语法:

    unique_ptr<Component[]> components( new Components[count] ); 
    //                  ^^
    //
    // Note brackets "[]" after the first occurrence of "Component" 
    // in unique_ptr template argument.
    //
    

    Q4:太好了!但是在资源释放代码不是使用普通 C++ delete(或delete[])执行的情况下,我是否也可以使用unique_ptr,而是使用一些自定义清理功能,例如@987654362 @ 用于 C &lt;stdio.h&gt; 文件(使用 fopen() 打开),或 CloseHandle() 用于 Win32 文件 HANDLEs(使用 CreateFile() 创建)?

    A4:这绝对有可能:您可以为std::unique_ptr 指定一个自定义删除器

    例如:

    // 
    // Custom deleter function for FILE*: fclose().
    //
    std::unique_ptr<FILE,          // <-- the wrapped raw pointer type: FILE*
                    int(*)(FILE*)> // <-- the custom deleter type: fclose() prototype
    myFile( fopen("myfile", "rb"), // <-- resource (FILE*) is returned by fopen()
            fclose );              // <-- the deleter function: fclose()
    
    
    
    //
    // Custom deleter functor for Win32 HANDLE: calls CloseHandle().
    //
    struct CloseHandleDeleter
    {
        // The following pointer typedef is required, since
        // the raw resource is HANDLE (not HANDLE*).
        typedef HANDLE pointer;
    
        // Custom deleter: calls CloseHandle().
        void operator()(HANDLE handle) const
        {
            CloseHandle(handle);
        }
    };
    
    std::unique_ptr<HANDLE, CloseHandleDeleter> myFile( CreateFile(....) );  
    

    【讨论】:

    • FWIW,你为不使用的矢量特征支付什么费用? (提示:没有)
    • 至少从内存占用来看,std::vector 可以使用 3 个指针,而不是 unique_ptr 只需一个。
    • A2:如果可能的话,一个更好的解决方案是有一个方法进行计算并返回 std::unique_ptr ,然后在初始化列表中使用它。
    • 我还没卖掉 :( 我无法想象有几个额外的指针是不行的,但是分配所有这些数组就可以了。
    • 如果你有一个 10,000x10,000 的矩阵,每个元素都是一个动态分配的数组,每个 vector 有 8 个字节的开销(如果与 unique_ptr 相比,有 2 个额外的指针),所以总开销是800,000,000 字节,即大约 760MB。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多