一些 std::unique_ptr 的用法和注意事项

7

C++11中,std::unique_ptr 的一些用途和注意事项是什么?

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

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

1个回答

17

让我们使用问答的格式来组织一些用途和注意事项。


Q1: 我想在我的类X中存储指向类Component指针
我不希望"容器"类X可复制; XComponent实例的唯一所有者。
我知道,拥有原始指针是一个不好的选择,并且可能导致内存泄漏(相反,观察原始指针是可以接受的)。 对于这个目的,我应该使用什么样的智能指针

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: 太好了!但是我能否在使用 unique_ptr 时,资源释放代码不使用普通的 C++ delete(或 delete[]),而是使用某些自定义清理函数,比如对于使用 fopen() 打开的 C <stdio.h> 文件,可以使用 fclose() 函数进行释放,或者对于 Win32 文件 HANDLE(使用 CreateFile() 创建)可以使用 CloseHandle() 函数?

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(....) );  

就我的经验而言,你不用的向量功能是不需要付费的。(提示:零成本) - R. Martinho Fernandes
至少从内存占用来看,std::vector 可以使用 3 个指针,而 unique_ptr 只需要一个。 - Mr.C64
A2:如果可能的话,更好的解决方案是编写一个方法来进行计算并返回std::unique_ptr,然后直接在初始化列表中使用它。 - stijn
3
我仍然不太确定 :( 我无法想象出有任何情况是几个额外指针不好,但分配所有那些数组就不行。 - R. Martinho Fernandes
2
如果您有一个矩阵10,000x10,000,每个元素都是动态分配的数组,每个“向量”有8字节的开销(与“unique_ptr”相比,多2个附加指针),因此总开销为800,000,000字节,即约760MB。 - Mr.C64
显示剩余2条评论

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