2 - unique_ptr

本文将对独享型智能指针unique_ptr做一些详细的介绍。

介绍

unique_ptr拥有资源的唯一所有权。当它被销毁或重置时,资源将自动释放。一般优先考虑使用它,因为消耗更小。它的一个优点是,内存和资源总会被释放,即使在执行return语句或抛出异常时也是如此

unique_ptr是一个泛型的智能指针,可以指向任何类型的内存,因此使用它时需要在后面跟上包裹着数据类型的尖括号。

创建unique_ptr

可以通过两种方式来创建unique_ptr:使用构造函数和使用 make_unique函数模板。

构造函数

使用构造函数的代码如下:

std::unique_ptr<Simple> pUniqueSimple(new Simple());
pUniqueSimple->doSth();

可以看到写法是比较复杂的,且如果Simple的构造函数出现异常,很有可能导致隐含的内存泄漏。

用VLD内存检测结果如下:

[推荐]make_unique

建议 始终使用make_unique()创建unique_ptr,这样写的代码不仅安全,而且可读性更好。

使用make_unique的代码如下:

std::unique_ptr<Simple> pUniqueSimple = std::make_unique<Simple>();
pUniqueSimple->doSth();

结果也是无泄漏的。

使用unique_ptr

裸指针老用法

和裸指针一样,unique_ptr也能使用 * 或 -> 对其进行解引用:

pUniqueSimple->go();
(*pUniqueSimple).go();

get()

使用get()可以直接获得底层裸指针,这可用于将智能指针传递给需要普通指针的函数:

void processData(Simple* pSimple) {/* ...... */}

// 使用get()获取底层指针
processData(pUniqueSimple.get());

需要注意的是,如果一定要使用它获取底层指针,那么一定不能 delete 它,否则会造成重复释放内存的错误

reset()

使用reset()可以单独释放智能指针的对象,也能让其释放后指向新的对象:

// 释放对象,置nullptr
uniquePtr.reset();
// 释放对象,将智能指针指向一个新的Simple实例
uniquePtr.reset(new Simple());

release()

使用release()可以断开unique_ptr与底层指针的连接。该方法返回对象的底层指针,然后将智能指针置空:

Simple* pSimple = uniquePtr.release();

自定义deleter

默认情况下,unique_ptr使用标准的 newdelete 进行内存分配,我们也能进行自定义,准确来说,是自定义deleter。

例如,下面是对int型变量进行自定义:

int* my_alloc(int value) 
{
    return new int(value);
}
void my_free(int* p)
{
    delete p;
}

// main
std::unique_ptr<int, decltype(&my_free)> myIntPtr(my_alloc(2333), my_free);

这段代码使用my_alloc()分配内存,unique_ptr 调用my_free()释放内存。自定义deleter的特性很有用,因为可管理的资源不仅仅是内存(还有文件/套接字等)

注意事项

  1. unique_ptr禁止复制构造函数,也禁止赋值运算符重载,因为它要独享这块内存。

  2. unique_ptr允许移动构造,移动赋值。使用移动语义(std::move())代表之前的对象已经失去了意义,移动操作自然不影响独占的特性。

  3. make_unique使用值初始化,例如基本类型被初始化为0,对象被默认构造。

    C++20中新增了使用默认初始化的make_unique_for_overwrite,对象被默认构造,值没有初始化,提高了一点效率,但要谨慎使用。

  4. 可以通过移动语义将unique_ptr对象转换成shared_ptr对象,这种情况很少出现,通常是因为设计失误了。

参考资料

  • 飘零的落花 - 现代C++详解
  • C++20高级编程