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
使用标准的 new 和 delete 进行内存分配,我们也能进行自定义,准确来说,是自定义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的特性很有用,因为可管理的资源不仅仅是内存(还有文件/套接字等)。
注意事项
unique_ptr
禁止复制构造函数,也禁止赋值运算符重载,因为它要独享这块内存。unique_ptr
允许移动构造,移动赋值。使用移动语义(std::move()
)代表之前的对象已经失去了意义,移动操作自然不影响独占的特性。make_unique
使用值初始化,例如基本类型被初始化为0,对象被默认构造。C++20中新增了使用默认初始化的
make_unique_for_overwrite
,对象被默认构造,值没有初始化,提高了一点效率,但要谨慎使用。可以通过移动语义将
unique_ptr
对象转换成shared_ptr
对象,这种情况很少出现,通常是因为设计失误了。
参考资料
- 飘零的落花 - 现代C++详解
- C++20高级编程