为什么使用智能指针?
智能指针使用的是RAII的思想,资源的获取和释放和对象的声明周期绑定(通过类),避免手动释放内存,对象声明周期结束自动调用析构函数释放,避免内存泄漏。
比如悬空指针(指针指向的地址已经被销毁),智能指针能避免这种情况发生。
C++98提供了auto_ptr模板的解决方案 C++11新增了unique_ptr、shared_ptr、weak_ptr
auto_ptr(C++17中被完全移除/弃用)
auto_ptr是C++98中定义的智能指针模板,可以将new获得的地址赋值给auto_ptr类型的指针,当对象过期时,调用析构函数中的delete释放内存。
1 2 3 4 5
| #include<memory> auto_ptr<string> str_ptr(new string("这是一个字符串")); auto_ptr<vector<int>> ve_ptr(new vector<int>()); auto_ptr<int> array_ptr(new int[10]);
|
对于类
1 2 3 4 5 6 7 8
| class T { public: int Display(){} } T *tptr = new T; auto_ptr<T> tptr1(new T); cout << tptr1->Display() << (*tptr).Display() << endl;
|
智能指针常用的三个函数
1 2 3 4 5 6 7 8 9 10
| auto_ptr<T> tptr(new T);
T *tmp = tptr.get(); cout << tmp->Display();
T *tmp2 = tptr.release(); delete tmp2;
tptr.reset(); tptr.reset(new T());
|
auto_ptr从C++11之后被抛弃的主要原因(被unique_ptr取代了)
- 复制或赋值都会改变资源的所有权
- 在STL容器中使用非常危险,因为容器中的元素必须支持可复制和赋值(别把指针传入容器,就算使用了std::move()避免了,在容器中修改值也寄了)
- 不支持对象数组的内存管理(🚫auto_ptr<int[]> array(new int[5]);)因为auto_ptr并不会使用delete[],会错误的使用delete释放,引发内存泄漏甚至崩溃(访问非法内存)
unique_ptr
- 同auto_ptr的特性一样,有排他所有权模式:两个指针不能指向同一个资源
- 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但是允许临时右值赋值构造和赋值
- 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象
- 在容器中保存指针是安全的
使用 std::move
可以把左值转换成右值
1 2 3 4 5 6 7 8 9 10 11 12
| unique_ptr<string> p1(new string("I'm Li Ming!")); unique_ptr<string> p2(new string("I'm age 22.")); unique_ptr<string> p3(std::move(p1)); p1 = std::move(p2);
vec.push_back(std::move(p3)); vec[0] = std::move(vec[1]);
unique_ptr<int[]> array(new int[5]);
|
除上述用法,其它与auto_ptr无差别。
初始化方法
1 2
| std::unique_ptr<std::string> p1(new std::string("Hello")); std::unique_ptr<std::string> p1 = std::make_unique<std::string>("Hello"); //C++14开始
|
使用 make_unique
减少了手动使用new的需求;它将对象的构造函数和内存分配结合在一起,避免了潜在的内存泄漏;支持数组的安全初始化,自动使用delete[]释放数组;提供更好的(一丢丢) 性能和内存管理。
内存管理陷阱
1 2 3 4 5
| unique_ptr<string> p1, p2; string *str = new string("内存管理陷阱"); p1.reset(str); p2.reset(str); cout << *p1 << endl;
|
shared_ptr
多个指针变量共享内存。记录引用特定内存对象的智能指针数量,当复制时,引用计数+1,当智能指针析构时,引用计数-1,如果计数为0,代表已经没有指针指向这块内存,我们就可以释放了。
1 2
| element_type* _Ptr{nullptr}; _Ref_count_base* _Rep{nullptr};
|
1.引用计数的使用
调用use_count函数可以获得当前托管指针的引用计数
1 2 3 4 5 6
| shared_ptr<T> p1; shared_ptr<T> p2(new T); p1 = p2; cout << p1.use_count() << endl; shared_ptr<T> p3(p2); cout << p1.use_count() << endl;
|
2.构造
1 2 3 4 5 6 7 8 9 10 11 12
| shared_ptr<T> p1; T *tptr = new T(1); p1.reset(tptr);
shared_ptr<T> p2(new T(2)); shared_ptr<T> p3(p1);
shared_ptr<T[]> p4; shared_ptr<T[]> p5(new T[5]{3,4,5,6,7});
shared_ptr<T> p6(NULL, DestructT()); shared_ptr<T> p7(new T(8), DestructT());
|
3.初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| shared_ptr<int> up1(new int(10)); shared_ptr<int> up2(up1);
shared_ptr<int> up3 = make_shared<int>(2); shared_ptr<string> up4 = make_shared<string>("string"); shared_ptr<T> up5 = make_shared<T>(9);
shared_ptr<int> up6(new int(10)); shared_ptr<int> up7(new int(11)); up6 = up7;
shared_ptr<int> up8(new int(10)); up8 = nullptr; up8 = NULL;
p.reset(); p.reset(p1); p.reset(p1,d);
std::swap(p1,p2); p1.swap(p2);
|
shared_ptr使用陷阱
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源。
比如A类中有B类的智能指针,B类中有A类的智能指针,相互持有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Boy() { void setGirlFriend(shared_ptr<Girl> _girlFriend) { this->girlFriend = _girlFriend; } shared_ptr<Girl> girlFriend; } class Girl() { void setBoyFriend(shared_ptr<Boy> _boyFriend) { this->boyFriend = _boyFriend; } shared_ptr<Boy> boyFriend; } void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); spGirl->setBoyFriend(spBoy); }
|
解读:当useTrap()被调用,会初始化两个共享智能指针,spBoy指向Boy()内存,spGirl指向Girl()内存,然后进入交叉引用阶段,Boy()中有一个gF指针指向Girl(),Girl()中有一个bF指针指向Boy()。
可以使用weak_ptr避免这种情况的发生。
weak_ptr
目的是为了配合shared_ptr而引用的一种智能指针来协助其工作的,它只可以从一个shared_ptr或者另一个weak_ptr对象构造,它的构造和析构不会引起计数的增加或减少。
weak_ptr没有重载 * 和 ->,但是可以使用lock获得一个可用的shared_ptr对象。
1 2 3 4 5 6 7 8 9
| weak_ptr wpGirl_1; weak_ptr wpGirl_2(spGirl); wpGirl_1 = spGirl;
wpGirl_1.use_count();
shared_ptr<Girl> sp_girl; sp_girl = wpGirl_1.lock(); sp_girl = NULL;
|
上述问题代码改成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Boy() { void setGirlFriend(shared_ptr<Girl> _girlFriend) { this->girlFriend = _girlFriend; } shared_ptr<Girl> girlFriend; } class Girl() { void setBoyFriend(shared_ptr<Boy> _boyFriend) { this->boyFriend = _boyFriend; } weak_ptr<Boy> boyFriend; } void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); spGirl->setBoyFriend(spBoy); }
|
直接解决问题!
weak_ptr的expired函数用法:判断当前weak_ptr智能指针是否还有托管的对象,有返回false,无返回true
如果返回true,相当于use_count()=0,已经没有托管对象了。可以在使用指针前加以判断,保证指针是有效的。
1 2 3
| weak_ptr<int> g; if(!g.expired()) cout << 1 << endl; else cout << 0 << endl;
|
智能指针的使用陷阱
- 不要把一个原生指针给多个智能指针管理;
- 记得使用u.release()的返回值,这个返回值是这块内存的唯一索引,没有释放的话就内存泄漏了;
- 禁止使用delete智能指针get()得到的指针;
- 禁止使用任何类型智能指针get()返回的地址去初始化另一个智能指针。