C++智能指针
智能指针
概念:
- 智能指针是原始指针的封装,其优点是会自动分配内存,不用担心潜在的内存泄露
普通指针的不足:
- new和new[]的内存需要用delete和delete[]释放
- 程序员的主观失误,忘了或漏了释放
- 程序员也不确定何时释放
智能指针的设计思路:
- 智能指针是类模板,在栈上创建智能指针对象
- 把普通指针交给智能指针对象
- 智能指针对象过期时,调用析构函数释放普通指针的内存
独占指针 unique_ptr
概念:
unique_ptr
**独享一个原始指针(也就是unique_ptr指向的对象)**,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁
源码简介:
1 |
|
功能描述:
- 在任何给定时候,只能有一个指针管理内存(也就是独占指针对象只管理一个指针资源)
- 当指针超出作用域时,内存将自动释放
- unique_ptr指针不可Copy,也不可以进行赋值,只可以Move
- 可以通过get()获取裸指针地址(&unique_ptr对象得到的是对象指针地址不是裸指针)
- 可以通过->调用成员函数
- 可以通过*调用dereferencing
作为函数的参数:
- 传引用(不能传值,因为unique_ptr没有拷贝构造函数)
- 裸指针
注意:
- 不要用同一个裸指针初始化多个unique_ptr对象
- 不要用unique_ptr管理不是new分配的内存
- 不支持指针的运算(+、-、++、–)
**创建方式(初始化)**:
通过已有裸指针创建
1
2
3
4
5
6
7
8
9class AA{
....
};
int main(){
AA* p=new AA("小明");
unique_ptr<AA> pu1=p;
return 0;
}通过new来创建
1
unique_ptr<AA> pu1(new AA("小明"));
通过std::make_unique创建(推荐)
1
unique_ptr<AA> pu1=make_unique<AA>("小明"); //C++14标准
内置函数和使用方法:
get()
:获取裸指针的地址1
2unique_ptr<int> p1=make_unique<int>(10);
cout<<p1.get()<<endl; //0x6498f94942b0release()
:释放对原始指针的控制权,将unique_ptr对象置为空,返回裸指针地址(之前管理的指针地址)。可用于把unique_ptr传递给子函数,子函数将负责释放对象1
2
3unique_ptr<int> p1=make_unique<int>(10);
cout<<p1.release()<<endl; //0x5fb45e2472b0
if(p1==nullptr) cout<<"p1为空"<<endl; //p1为空std::move()
:转移对原始指针的控制权。(可用于把unique_ptr传递给子函数,子函数形参也是unique_ptr,传值的方法)1
2
3
4
5
6
7
8
9
10
11
12
13
14//函数func4()需要一个unique_ptr对象,并且会对这个对象负责
void func4(unique_ptr<AA> a){
cout<<a->m_name<<endl;
}
int main(int argc, char *argv[])
{
unique_ptr<AA> p(new AA("小明"));
cout<<"开始调用函数.\n";
func4(std::move(p)); //将原始指针的控制权交给func4函数的形参
cout<<"调用函数完成.\n";
return 0;
}reset()
:释放unique_ptr指向资源,以及更改指向1
2
3
4
5
6//函数原型
void reset(T* _ptr=(T*)nullptr);
unique_ptr<AA>p(new AA("小明"));
p.reset(); //释放p对象所指向的资源对象
p.reset(nullptr); //释放p对象所指向的资源对象
p.reset(new AA("小蓝")); //释放p所指向对象,并指向新的对象swap()
:交换两个unique_ptr所指向的对象的控制权1
2
3
4
5
6
7
8
9
10
11//函数原型
void swap(unique_ptr<T>&_Right);
unique_ptr<AA>p1(new AA("小明"));
unique_ptr<AA>p2(new AA("小红"));
p1.swap(p2);
cout<<p1->m_name<<endl;
cout<<p2->m_name<<endl;
//输出:
//小红
//小明[]
:unique_ptr重载了操作符[],提供了支持数组的具体化版本,操作符[]返回的是引用,可以作为左值使用
1
2
3
4
5//unique_ptr<int[]> parr1(new int[3]); //不指定初始值
unique_ptr<int[]> parr1(new int[3](33,22,11)); //指定初始值
cout<<"parr1[0]="<<parr1[0]<<endl;
cout<<"parr1[1]="<<parr1[1]<<endl;
cout<<"parr1[2]="<<parr1[2]<<endl;
使用技巧:
如果源unique_ptr对象是一个临时右值(一般是函数的返回值),编译器允许将这个unique_ptr赋给另一个unique_ptr对象。如果源unique_ptr将存在一段时间,则编译器不允许赋值!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int>func(){
unique_ptr<int> pp=make_unique<int>(100);
return pp;
}
int main(int argc, char *argv[])
{
unique_ptr<int> p1=make_unique<int>(10);
unique_ptr<int> p2;
p2=p1; //错误,不可以
p2=unique_ptr<int>(new int(1)); //使用匿名对象赋值,可以
p1=func(); //利用函数返回值赋值,可以
std::cout<<*p1<<std::endl;
return 0;
}用nullptr给unique_ptr赋值将会释放对象,空的unique_ptr==nullptr
一些情况使用技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52#include <iostream>
#include <memory>
using namespace std;
class AA{
public:
string m_name;
AA(){
cout<<m_name<<"调用构造函数AA().\n";
}
AA(const string &name):m_name(name){
cout<<"调用构造函数AA("<<m_name<<").\n";
}
~AA(){
cout<<"调用了析构函数~AA("<<m_name<<").\n";
}
};
//函数func1()需要一个原始指针,但不对指针负责
void func1(const AA* a){
cout<<a->m_name<<endl;
}
//函数func2()需要一个裸指针,并且对这个指针负责
void func2(AA* a){
cout<<a->m_name<<endl;
delete a;
}
//函数func3()需要一个unique_ptr对象,不会对这个对象负责
void func3(const unique_ptr<AA> &a){
cout<<a->m_name<<endl;
}
//函数func4()需要一个unique_ptr对象,并且会对这个对象负责
void func4(unique_ptr<AA> a){
cout<<a->m_name<<endl;
}
int main(int argc, char *argv[])
{
unique_ptr<AA> p(new AA("小明"));
cout<<"开始调用函数.\n";
func1(p.get());
func2(p.release());
func3(p);
func4(std::move(p));
cout<<"调用函数完成.\n";
return 0;
}unique_ptr也可以像普通指针一样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样
unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32#include <iostream>
#include <memory>
using namespace std;
class AA{
public:
string m_name;
AA(){
cout<<m_name<<"调用构造函数AA().\n";
}
AA(const string &name):m_name(name){
cout<<"调用构造函数AA("<<m_name<<").\n";
}
~AA(){
cout<<"调用了析构函数~AA("<<m_name<<").\n";
}
};
unique_ptr<AA> p1(new AA("小明全局"));
int main(int argc, char *argv[])
{
unique_ptr<AA> p(new AA("小明局部"));
//return 0; 使用return全部自动释放
exit(0); //局部无法进行释放
}
//打印日志
|| 调用构造函数AA(小明全局).
|| 调用构造函数AA(小明局部).
|| 调用了析构函数~AA(小明全局).
|| Exited with code 0
共享指针 shared_ptr
概念:
shared_ptr
共享它指向的对象,多个shared_ptr可以指向(关联)相同的对象,在内部采用计数机制来实现,共享不是复制,资源只有一个- 当新的shared_ptr与对象关联时,引用计数增加1
- 当shared_ptr超出作用域时,引用计数减1。
- 当引用计数为0时,则表示没有任何shared_ptr与对象相联,则释放该对象
基本用法:
注:shared_ptr的构造函数也是explicit,但是,没有删除拷贝构造函数和赋值函数
初始化
:1
2
3
4
5
6
7
8
9
10
11
12
13
14//方法1:
shared_ptr p(new AA("小明")); //分配内存并初始化
//方法2[推荐]:
shared_ptr p=make_shared<AA>("小明"); //C++11标准,效率更高
//方法3:
AA* p=new AA("小明");
shared_ptr<AA> p0(p); //用已存在的裸指针初始化
//方法4:
shared_ptr<AA> p0(new AA("小明"));
shared_ptr<AA> p1(p0); //用已存在的shared_ptr初始化,计数加1
shared_ptr<AA> p2=p0; //用已存在的shared_ptr初始化,计数加1use_count()
:显示shared_ptr所指向的资源引用次数1
2
3
4shared_ptr<AA> p0=make_shared<AA>("小明");
shared_ptr<AA> p1(p0);
shared_ptr<AA> p2=p0;
cout<<p0.use_count()<<"\n"; //3get()
:显示shared_ptr所指向的资源地址unique()
:判断shared_ptr所指向的资源是否被其他shared_ptr对象所共享,如果use.count()为1,返回true,否则返回false1
2
3
4
5
6shared_ptr<AA> p0=make_shared<AA>("小明");
shared_ptr<AA> p1(p0);
shared_ptr<AA> p2=p0;
cout<<p0.unique()<<"\n"; //0
shared_ptr<AA> p3=make_shared<AA>("111");
cout<<p3.unique()<<"\n"; //1=
:shared_ptr重载了=操作符,支持赋值,因为将右值shared_ptr赋值给了左值shared_ptr,因此右值shared_ptr所指向资源计数+1,则左值shared_ptr所指向资源计数-11
2
3
4
5
6
7
8
9
10
11
12
13int main(int argc, char *argv[])
{
shared_ptr<AA> p0=make_shared<AA>("小明");
shared_ptr<AA> p1(p0);
shared_ptr<AA> p2=p0;
shared_ptr<AA> p3=make_shared<AA>("111");
shared_ptr<AA> p4=p3;
shared_ptr<AA> p5=p3;
p2=p5; //p3+1,p0-1
cout<<p0.use_count()<<"\n"; //2
cout<<p3.use_count()<<"\n"; //4
return 0;
}[]
:shared_ptr重载了操作符[],提供了支持数组的具体化版本,操作符[]返回的是引用,可以作为左值使用
std::move()
:可以将转移对原始指针的控制权,还可以将unique_ptr转移成shared_ptr(反过来不行)reset()
:改变与资源的关联关系,以及指向1
2
3shared_ptr<AA> p=make_shared<AA>("小明");
p.reset(); //解除与资源的关系,资源的引用计数减1
p.reset(new AA("111")); //解除与资源的关系,原资源的引用计数-1,关联新的资源,新资源引用计数+1swap()
:交换两个shared_ptr的控制权1
2//函数原型
void swap(shared_ptr<T> &_Right);
作为函数的参数:
- 传引用(不能传值,因为unique_ptr没有拷贝构造函数)
- 裸指针
注意:
- 不要用同一个裸指针初始化多个shared_ptr对象
- 不要用shared_ptr管理不是new分配的内存
- 不支持指针的运算(+、-、++、–)
使用技巧:
- 用nullptr给shared_ptr赋值时将计数减1,如果计数为0,将释放对象,空的shared_ptr==nullptr
- shared_ptr也可以像普通指针一样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样
- shared_ptr不是绝对安全,如果程序中调用exit()退出,全局的shared_ptr可以自动释放,但局部的shared_ptr无法释放
- 如果unique_ptr能解决问题,就不要在用shared_ptr,unique_ptr的效率更高,占用的资源更少
shared_ptr的线程安全性:
- shared_ptr的引用计数本身是线程安全(引用计数是原子操作)
- 多个线程同时读同一个shared_ptr对象是线程安全的
- 如果多个线程对同一个shared_ptr对象进行读和写,则需要加锁
- 多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护
shared_ptr存在问题:
- shared_ptr内部维护一个共享的引用计数器,多个shared_ptr可以指向同一个资源
- 如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放
1 |
|
智能指针删除器
概念:
- 在默认情况下,智能指针过期时,它会自动调用缺省的删除器来删除资源
- 程序员可以自定义删除器,改变智能指针释放资源的行为
- 删除器可以是全局函数、仿函数和Lambda表达式,形参为原始指针
示例:
1 |
|
弱智能指针 weak_ptr
引言:为了解决shared_ptr循环引用带来的未自动清理的情况,C++还引入了weak_ptr指针来帮助shared_ptr解决
1 |
|
概念:
weak_ptr
是为了配合shared_ptr而引入的,它指向一个由shared_ptr管理的资源但不影响资源的生命周期,也就是说,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数- 不论是否有weak_ptr指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放
- weak_ptr更像是shared_ptr的助手而不是智能指针
1 |
|
基本用法:
operator=();
:重载了=操作符,可以将shared_ptr或weak_ptr赋值给weak_ptrexpired()
:判断weak_ptr所指资源是否过期(是否被销毁)lock()
:将weak_ptr升级成shared_ptr,如果指向资源存在,将weak_ptr升级成shared_ptr,返回shared_ptr;如果资源已过期,返回空的shared_ptrreset()
:将当前weak_ptr指针置为空swap()
:交换两个weak_ptr控制权
注意:
- weak_ptr不控制对象的生命周期,但是它可以知道对象是否还活着
- lock()函数是线程安全的,因为它是一个原子操作的(对于多线程最好使用lock而不是expired)
(多线程)错误做法:
1 |
|
(多线程)正确做法:
1 |
|