7.5智能指针
文章目录
永远不要将资源分配结果赋值给原始指针。无论使用哪种资源分配方法,都应当立即将资源指针存储在智能指针unique ptr或 shared ptr中,或使用其他RAII类。RAII代表Resource Acquisition IsInitialization(资源获取即初始化)。RAII 类获取某个资源的所有权,并在适当的时候进行释放。
1.unique_ptr
unique_ptr拥有资源的唯一所有权。当 unique_ptr被销毁或重置时,资源将自动释放。unique_ptr的一个优点是,内存和资源总会被释放,即使在执行return 语句或抛出异常时也是如此。例如,当函数有多个返回语句时,这简化了编码,因为不必记住在每个return语句之前释放资源。
作为经验法则,总是应该将动态分配的有唯一所有者的资源保存在 unique_ptr 的实例中。
1.1创建unique_ptr
auto uPtr {std::make_unique<T>(parameter)};
make_unique()
使用值初始化,例如,基本类型被初始化为零,对象被默认构造。如果不需要此值初始化,例如在对性能有严格要求的代码中,可以使用C+20中引入的 make_unique_for_overwrite()
函数,该函数使用默认初始化。对于基本类型,这意味着它们根本没有初始化,包含它们所在内存中的任何内容,而对象仍然是默认构造的。还可以通过以下语句创建:
std::unique_ptr<T> uPtr {new T{}};
1.2使用unique_ptr
get()
方法用于直接获取底层指针
reset()
可释放unique_ptr的底层指针,并根据需要将其变为另一个指针
uPtr.reset();
uPtr.reset(new T{});
可使用release()
断开unique ptr与底层指针的连接。release()
方法返回资源的底层指针,然后将智
能指针设置为 nullptr
。实际上,智能指针失去对资源的所有权,你需要负责在用完资源时释放资源。
由于unique_ptr
代表唯一拥有权,因此无法复制它!但是,可使用移动语义将一个unique_ptr
移
到另一个。std∶move()
工具函数可以用于显式移动unique_ptr
的所有权:
class Foo
{
public:
Foo(unique_ptr<int> data): m_data{move(data)}{}
private:
unique_ptr<int> m_data;
};
auto myIntSmartPtr {make_unique<int>(42)};
Foo f{move(myIntSmartPtr)};
1.3unique_ptr与C风格数组
略
1.4自定义deleter
int* my_alloc(int value){return new int{value};}
void my_free(int* p){delete p;}
int main()
{
unique_ptr<int, decltype(&my_free)> myIntSmartPtr {my_alloc(42), my_free};
}
这项特性适用于当unique_ptr离开作用域时释放资源,如关闭文件或网络套接字。
2.shared_ptr
std::shared_ptr
是一个可复制的支持共享所有权的智能指针,通过引用计数来解决资源释放的问题。
2.1创建并使用shared_ptr
auto sPtr {make_shared<T>()};
shared_ptr
也支持get()
和reset()
方法,只是当调用reset()
时,仅在最后的shared_ptr
销毁或重置时,才释放底层资源。shared_ptr
不支持release()
方法。
可使用use_count()
方法检索共享同一资源的shared_ptr实例数。
自定义deleter语法如下:
shared_ptr<int> myIntSmartPtr {malloc_int(42), my_free};
2.2引用计数的必要性
作为一般概念,引用计数(reference counting)用于跟踪正在使用的某个类的实例或特定对象的个数。引用计数的智能指针跟踪为引用一个真实指针(或某个对象)而建立的智能指针的数目。每次复制这样一个引用计数的智能指针时,都会创建一个指向同一资源的新实例,并且引用计数会增加。当此类智能指针实例超出作用域或被重置时,引用计数将减少。当引用计数降至零时,资源不再有其他所有者,因此智能指针将释放资源。
唯一安全得到多个指向同一内存的 shared_ptr 实例的方法是创建 shared_ptr 的副本。
Simple* mySimple{ new Simple{} };
shared_ptr<Simple> sPtr1{mySimple};
shared_ptr<Simple> sPtr2{mySimple};//引用计数不会增加,会导致双重释放
2.3强制转换shared_ptr
const_pointer_cast()
dynamic_pointer_cast()
static_pointer_cast()
reinterpret_pointer_cast()
2.4别名
shared_ptr支持所谓的别名, 这允许一个shared_ptr与另一个shared_ptr共享一个指针(拥有的指针), 但是指向不同的对象(存储的指针). 这可用于使用一个shared_ptr拥有一个对象本身的同时, 指向该对象的成员:
class Foo{
public:
Foo(int value): m_data(value){}
int m_data;
};
auto foo {make_shared<Foo>(42)};
auto aliasing {shared_ptr<int>{foo, &foo->m_data}};
仅当两个shared_ptr都销毁时, 才销毁对象Foo.
“拥有的指针”用于引用计数, 对指针解引用或调用它的get()
时, 将返回“存储的指针”.
3.weak_ptr
weak_ptr 可包含由 shared_ptr管理的资源的引用。weak_ptr不拥有这个资源,所以不能阻止shared_ptr释放资源。weak_ptr销毁时(例如离开作用域时)不会销毁它指向的资源,然而,它可用于判断资源是否已经被关联的shared_ptr释放了。weak_ptr 的构造函数要求将一个shared_ptr 或另一个weak_ptr作为参数。为了访问weak_ptr中保存的指针,需要将 weak _ptr转换为 shared_ptr。这有两种方法∶
- 使用 weak_ptr实例的
lock()
方法,这个方法返回一个shared_ptr。如果同时释放了与weak_ptr关联的 shared_ptr,返回的 shared_ptr是nullptr
。 - 创建一个新的 shared_ptr 实例,将 weak_ptr 作为 shared_ptr 构造函数的参数。如果释放了与weak_ptr关联的 shared_ptr,将抛出
std::bad_weak_ptr
异常。
4.向函数传递参数
仅当涉及所有权转移或所有权共享时,接受指针作为其参数之一的函数才应接受智能指针。要共享 shared_ptr的所有权,只需要接受按值传递的shared_ptr作为参数。类似地,要转移unique_ptr 的所有权,只需要接受按值传递的 unique_ptr 作为参数。后者需要使用移动语义,第9章将详细讨论。
如果既不涉及所有权转移也不涉及所有权共享,那么函数应该简单地使用 non-const 的引用或 const 引用作为参数;或者如果 nullptr是参数的有效值,则应该使用原始指针作为参数。拥有诸如const shared_ptr&或 const unique_ptr&的参数类型没有多大意义。
5.从函数中返回
标准智能指针shared_ptr、unique_ptr 和weak_ptr可以简单有效地从函数中按值返回.
6.enable_shared_from_this
std:enable_shared_from_this
派生的类允许对象调用方法,以安全地返回指向自己的 shared_ptr或
weak_ptr。如果没有这个基类,返回有效的 shared_ptr或weak_ptr的一种方法是将weak_ptr作为成员添加到类中,并返回它的副本或返回由它构造的 shared_ptr。enable_shared_from_this类给派生类添加了以下两个方法∶
● shared_from_this()
——返回一个shared_ptr,它共享对象的所有权。
● weak_from_this()
——返回一个 weak_ptr,它跟踪对象的所有权。
class Foo: public enable_shared_from_this<Foo>
{
public:
shared_ptr<Foo> getPointer(){
return shared_from_this();
}
}
int main()
{
auto ptr1 {make_shared<Foo>()};
auto ptr2 {ptr1->getPointer()};
}
注意,仅当对象的指针已经存储在 shared_ptr时,才能使用对象上的 shared_from_this()
。否则,将会抛出 bad_weak_ptr
异常。另一方面,调用weak_from_this()
总是允许的,但当对象的指针还未存储在 shared_ptr时,将会返回一个空的 weak_ptr。
7.过时的、移除的auto_ptr
略