目录
1.智能指针简介
C++11中有:
unique_ptr
shared_ptr
weak_ptr
三种智能指针,定义在<memory>头文件中。可以对动态资源进行管理,保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。
2.unique.ptr
unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象,不支持拷贝和赋值操作,(通过禁止复制语义、只有移动语义来实现,因为移动语义在转移指针变量后会将旧指针置空,不可能让多个指针指向同一块内存)。
2.1 unique_ptr指针本身的生命周期
从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
2.2 unique_ptr的相关操作
<1> 移动语义,使用move()函数,将unique_ptr_1指向的内存转移给unique_ptr_2;
<2> reset()
reset() 不带参数的情况:释放智能指针所指向的对象,并将智能指针置空。
reset() 带参数的情况:释放智能指针所指向的对象,并让该智能指针指向新的对象。
<3> release()
切断智能指针和所指向对象之间的联系,但不会释放对象所在的内存空间,返回裸指针,并将该智能指针置空。返回的这个裸指针我们可以手工 delete 来释放,也可以用来初始化另外一个智能指针,或者给另外一个智能指针赋值。
<4> get()
返回智能指针中保存的裸指针,
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> up1(new int(11)); // 无法复制的unique_ptr
//unique_ptr<int> up2 = up1; // err, 不能通过编译
cout << *up1 << endl; // 11
unique_ptr<int> up3 = move(up1); // 现在p3是数据的唯一的unique_ptr
cout << *up3 << endl; // 11
//cout << *up1 << endl; // err, 运行时错误
up3.reset(); // 显式释放内存
up1.reset(); // 不会导致运行时错误
//cout << *up3 << endl; // err, 运行时错误
unique_ptr<int> up4(new int(22)); // 无法复制的unique_ptr
up4.reset(new int(44)); //"绑定"动态对象
cout << *up4 << endl;
up4 = nullptr;//显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价
unique_ptr<int> up5(new int(55));
int *p = up5.release(); //只是释放控制权,不会释放内存
cout << *p << endl;
//cout << *up5 << endl; // err, 运行时错误
delete p; //释放堆区资源
unique_ptr<string> ps1(new string("I Love China1"));
string* ps = ps1.get();
return 0;
}
3. shared_ptr
shared_ptr允许多个该类型的智能指针共享的“拥有”同一对象的内存,通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。
shared_ptr提供拷贝构造函数和赋值重载函数;也提供带右值引用的移动构造和移动赋值重载函数所以可以共享的拥有同一块内存。
3.1 shared_ptr的相关操作
- get:返回指向被管理对象的指针,不建议使用
- reset:替换被管理的对象,释放原来管理的资源,管理新的资源
- release:shared_ptr不提供解除智能指针和资源管理关系的函数
- use_count: 显示引用计数
- swap:交换被管理对象的所有权
#include <memory> #include <iostream> uisng namespace std; int main() { shared_ptr<int> sp1(new int(22)); shared_ptr<int> sp2 = sp1; cout << "count: " << sp2.use_count() << endl; //打印引用计数 cout << *sp1 << endl; // 22 cout << *sp2 << endl; // 22 sp1.reset(); //显式让引用计数减1 cout << "count: " << sp2.use_count() << endl; //打印引用计数 cout << *sp2 << endl; // 22 return 0; }
4. weak_ptr
weak_ptr
本身也是一个模板类,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr
来使用,可以将shared_ptr
的对象赋值给weak_ptr
,并且这样并不会改变引用计数的值。
4.1 weak_ptr的作用
shared_ptr
可以说近乎完美,但是通过引用计数实现的它,虽然解决了指针独占的问题,但也引来了引用成环的问题,这种问题靠它自己是没办法解决的,所以在C++11的时候将shared_ptr
和weak_ptr
一起引入了标准库,用来解决循环引用的问题。
4.1.1 什么是shared_ptr的引用成环?
定义两个类,每个类中又包含一个指向对方类型的智能指针作为成员变量,然后创建对象,设置完成后查看引用计数后退出。
class B;
class A
{
public:
A() { cout << "A() called! " << endl; }
~A() { cout << "~CA() called! " << endl; }
void set_ptr(shared_ptr<B>& ptr) {
ptr_b = ptr; }
void b_use_count() {
cout << "b use count : " << ptr_b.use_count() << endl; }
void show() {
cout << "this is class A!" << endl; }
private:
shared_ptr<B> ptr_b;
};
class B
{
public:
B() { cout << "B() called! " << endl; }
~B() { cout << "~B() called! " << endl; }
void set_ptr(shared_ptr<A>& ptr) { ptr_a = ptr; }
void a_use_count() { cout << "a use count : " << ptr_a.use_count() << endl; }
void show() { cout << "this is class B!" << endl; }
private:
shared_ptr<A> ptr_a;
};
void test_refer_to_each_other()
{
shared_ptr<A> M(new A());
shared_ptr<B> N(new B());
cout << "M use count : " << M.use_count() << endl;
cout << "N use count : " << N.use_count() << endl;
M->set_ptr(N);
N->set_ptr(M);
cout << "M use count : " << M.use_count() << endl;
cout << "N use count : " << N.use_count() << endl;
}
运行结果:
A() called!
B() called!
m use count : 1
n use count : 1
m use count : 2
n use count : 2
在创建完两个shared_ptr M 和shared_ptr N后,内存示意图如下:
运行 M->set_ptr 和 N->set_ptr后 ,内存示意图为:
运行 M->set_ptr 和 N->set_ptr 将各自对象中的指针变量指向对方,增加了两个对象的引用计数,构成了循环引用。当程序运行完毕后,两个对象都没有运行析构函数,因为shared_ptr只有当引用计数为0时才会释放对象的内存,像上图这种成员变量指针互相指的情况,类似与操作系统中的死锁,对象的引用计数永远无法可能为0;
4.2 使用weak_ptr解决shared_ptr的引用成环问题
解决这种状况的办法就是将两个类中的任意一个类中的成员变量改为weak_ptr
,因为weak_ptr
不会增加引用计数,使得引用不会成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将B
中的成员变量改为weak_ptr
对象,代码如下
class B;
class A
{
public:
A() { cout << "A() called! " << endl; }
~A() { cout << "~CA() called! " << endl; }
void set_ptr(shared_ptr<B>& ptr) {
ptr_b = ptr; }
void b_use_count() {
cout << "b use count : " << ptr_b.use_count() << endl; }
void show() {
cout << "this is class A!" << endl; }
private:
shared_ptr<B> ptr_b;
};
class B
{
public:
B() { cout << "B() called! " << endl; }
~B() { cout << "~B() called! " << endl; }
void set_ptr(shared_ptr<A>& ptr) { ptr_a = ptr; }
void a_use_count() { cout << "a use count : " << ptr_a.use_count() << endl; }
void show() { cout << "this is class B!" << endl; }
private:
weak_ptr<A> ptr_a;
};
void test_refer_to_each_other()
{
shared_ptr<A> M(new A());
shared_ptr<B> N(new B());
cout << "M use count : " << M.use_count() << endl;
cout << "N use count : " << N.use_count() << endl;
M->set_ptr(N);
N->set_ptr(M);
cout << "M use count : " << M.use_count() << endl;
cout << "N use count : " << N.use_count() << endl;
}
运行结果:
A() called!
B() called!
m use count : 1
n use count : 1
m use count : 1
n use count : 2
~A() called!
~B() called!
内存示意图:
由于weak_ptr不会增加引用计数,所以指向对象A的引用计数为1,当程序运行完后,shared_ptr M和shared_ptr N 都被释放,对象A的引用计数变为0,此时对象A可以被析构,A析构后,ptr_a被释放,指向对象B的引用计数也为0,然后对象B也可以被析构 ;
注意
weak_ptr
虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。weak_ptr
接受shared_ptr
类型的变量赋值,但是反过来需要使用lock
函数。//weak_ptr<B> a = new A(); 错误,weak_ptr无法直接指向对应类型的对象,只能通过shared_ptr的赋值 shared_ptr<B> b(new B()); //weak_ptr<B> c = b; 可以 weak_ptr<B> c; b = c.lock();
weak_ptr
设计之初就是为了服务于shared_ptr
的,所以不增加引用计数就是它的核心功能。
4.3 weak_ptr常用操作
成员方法 | 功能 |
---|---|
operator=() | 重载= 赋值运算符,weak_ptr 指针可以直接被weak_ptr 或者shared_ptr 类型指针赋值。 |
swap(x) | 其中x 表示一个同类型的weak_ptr 类型指针,该函数可以互换2个共同类型weak_ptr 指针的内容。 |
reset() | 将当前weak_ptr 指针置为空指针。 |
use_count() | 查看指向和当前weak_ptr 相同的shared_ptr 指针的数量。 |
expired() | 判断当前weak_ptr 指针是否过期(指针为空,或者指向的堆内存已经被释放),过期返回1,没过期返回0 |
lock() | 如果当前weak_ptr 已经过期,则该函数会返回一个空的shared_ptr 指针;反之,该函数返回一个和当前weak_ptr 指针指向相同的shared_ptr 指针。 |