7

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

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

std::unique_ptr还可以使用自定义删除机制与资源一起使用吗?

4

1 回答 1

18

让我们使用问答格式来组织一些用途和陷阱。


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

A1: C++11std::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=在构造函数中使用普通分配将新创建的分配Componentunique_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[],而是使用一些自定义清理函数的情况下使用,例如fclose()C<stdio.h>文件(用fopen()CloseHandle()HANDLECreateFile()

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(....) );  
于 2013-07-30T10:52:45.350 回答