C++11实用特性3 --智能指针
1 智能指针
在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数
,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件<memory>
:
- std::shared_ptr:共享的智能指针
- std::unique_ptr:独占的智能指针
- std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。
shared和unique的区别:
使用共享的智能指针可以有多个shared_ptr指向同一块内存,每一个智能指针内部都有计数,指向的指针每多一个,内部计数就会加1,析构一个就减1;当计数值为1时,这个时候再析构时,指针会先析构掉这片内存,就像我们调用delete一样,然后再调用自己的析构函数把自己析构掉
如果使用独享的指针unique_ptr,一块内存只能由一个unique_ptr进行管理,如果有其它的unique想要管理,就只能用move进行资源转移。它的内部计数不能超过1.
2 shared_ptr
1 初始化
初始化方式一共有4种
- 通过构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);
// 使用智能指针管理一块 int 型的堆内存
shared_ptr<int> ptr1(new int(520));
// 使用智能指针管理一块字符数组对应的堆内存
shared_ptr<char> ptr2(new char[12]);
// 创建智能指针对象, 不管理任何内存
shared_ptr<int> ptr3;
// 创建智能指针对象, 初始化为空
shared_ptr<int> ptr4(nullptr);
2. 通过拷贝和移动构造函数初始化
当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被自动调用了。
// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
shared_ptr<int> ptr1(new int(520));
//调用拷贝构造函数
shared_ptr<int> ptr2(ptr1);
shared_ptr<int> ptr3 = ptr1;
//调用移动构造函数
shared_ptr<int> ptr4(std::move(ptr1));
std::shared_ptr<int> ptr5 = std::move(ptr2);
如果使用拷贝的方式初始化共享智能指针对象,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加;如果使用移动的方式初始智能指针对象,只是转让了内存的所有权,管理内存的对象并不会增加,因此内存的引用计数不会变化。
3.通过std::make_shared初始化(主要方法)
通过C++提供的std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数原型如下:
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
- T:模板参数的数据类型
- Args&&... args :要初始化的数据,如果是通过make_shared创建对象,需按照构造函数的参数列表指定
使用示例:
class Test
{
public:
Test()
{
cout << "construct Test..." << endl;
}
Test(int x)
{
cout << "construct Test, x = " << x << endl;
}
Test(string str)
{
cout << "construct Test, str = " << str << endl;
}
~Test()
{
cout << "destruct Test ..." << endl;
}
};
int main()
{
// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
shared_ptr<int> ptr1 = make_shared<int>(520);
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
shared_ptr<Test> ptr2 = make_shared<Test>();
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
shared_ptr<Test> ptr3 = make_shared<Test>(520);
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
shared_ptr<Test> ptr4 = make_shared<Test>("我是要成为海贼王的男人!!!");
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
return 0;
}
使用std::make_shared()模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数。
4.通过reset方法初始化
对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。
2 使用
就像对象指针一样使用即可,如果初始化智能指针的时候用类,那么可以用->
来调用对象的成员函数。或者可以用.
来调用属于智能指针的方法,比如get()
3 指定删除器
当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。
#include <iostream>
#include <memory>
using namespace std;
// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p)
{
delete p;
cout << "int 型内存被释放了...";
}
int main()
{
shared_ptr<int> ptr(new int(250), deleteIntPtr);
return 0;
}
在C++11中使用shared_ptr管理动态数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象,具体的处理代码如下:
int main()
{
shared_ptr<int> ptr(new int[10], [](int* p) {delete[]p; });
return 0;
}
在删除数组内存时,除了自己编写删除器,也可以使用C++提供的std::default_delete
shared_ptr<int> ptr(new int[10], default_delete<int[]>());
3 weak_ptr
弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的内存,不管有多少shared_ptr,我们始终关注的都是那片内存空间的使用情况
1 初始化
// 默认构造函数
constexpr weak_ptr() noexcept;
// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
// 通过shared_ptr对象构造
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;
shared_ptr<int> sp(new int);
weak_ptr<int> wp1;
weak_ptr<int> wp2(wp1);
weak_ptr<int> wp3(sp);
weak_ptr<int> wp4;
wp4 = sp;
weak_ptr<int> wp5;
wp5 = wp3;
2 常用方法
- use_count()方法可以获得当前所观测资源的引用计数
- expired()方法来判断观测的资源是否已经被释放
- lock()方法来获取管理所监测资源的shared_ptr对象
- reset()方法来清空对象,使其不监测任何资源
3 shared_ptr的使用注意事项:
- 不能用一个原始地址(new出来的那个地址)初始化多个共享智能指针,因为两个智能指针对象互相不知道对方的存在,各自的引用计数都为1,释放的时候就会造成重复析构,共享智能指针的计数作用是用一个智能指针去初始化另一个才知道的
- 函数不能返回管理了this的共享智能指针对象,本质和第一条一样
- 共享智能指针不能循环引用,指两个智能指针对象不能互相存储,解决方式就是用shared_ptr