指针指向某块内存区域,使用普通指针会导致一些程序错误
内存泄漏:如果指针指向的内存是动态分配的,那么即使指针离开了作用域,动态分配的内存也不会自动释放,如果动态分配的内存不手动释放,则会导致内存泄漏
悬空指针:如果指针指向一个已经被释放的内存区域,那么这个指针就是悬空指针,使用悬空指针会导致不可预料的结果
野指针:定义了一个指针,却未初始化指向一个有效的内存区域,这个指针就成了野指针,使用野指针访问内存,一般会导致segmentation fault错误
使用智能指针可以避免上述错误的发生
智能指针是一个对象,它封装了一个指向另一个对象的指针,当智能指针对象离开作用域后,会被自动销毁,销毁过程中会调用析构函数来删除封装的对象

在c++中使用智能指针,你需要包含头文件 #include<memory>
演示类
之后的演示代码都用这个矩形类进行演示
class Rect
{
public:
Rect(int InWidth,int InHeight):Width(InWidth),Height(InHeight){ cout<<"构造函数调用"<<endl;};
~Rect(){ cout <<"当前面积为:"<< Width*Height <<"析构函数调用"<<endl; };
int area() const { return Width*Height; }
private:
int Width;
int Height;
};
unique_ptr
unique_ptr与它管理的指针是一对一的关系,也就是不能有两个unique_ptr对象指向同一个地址
unique_ptr在离开作用域时,会自动销毁,调用所包含对象的析构函数
创建方法
常用下面两种方法创建
unique_ptr<T> ptr = make_unique<T>(参数); //传入构造函数的参数
unique_ptr<T> ptr(new T(参数));
示例:
unique_ptr<int> p1 = make_unique<int>(2);
cout<< *p1 <<endl;
unique_ptr<Rect> p2 = make_unique<Rect>(2,4);
cout<<"p2的面积是:"<<p2->area()<< endl;
unique_ptr<Rect> p3(new Rect(5,9));
cout<<"p3的面积是:"<<p3->area()<< endl;
常用方法
unique_ptr重载了 * 和 ->运算符,让它可以像普通指针一样使用
//返回unique_ptr管理的原始指针
T* get();
//重载->运算符,内部使用了get()将操作转发给原始指针
T* operator->();
//重载*运算符,内部使用了get()将操作转发给原始指针
T& operator*();
//用于解除unique_ptr封装,返回原始指针,返回的指针不再受智能指针管理,需要注意手动释放
T* release();
//删除原有对象,接管新的对象
void reset(T* newObject);
//与其他unique_ptr交换管理的对象
void swap(unique_ptr<T>& other);
删除对象
当unique_ptr 离开其包含对象的作用域时,会删除包含的对象,并调用析构函数
{
unique_ptr<Rect> p1 = make_unique<Rect>(1,1);
}
将unique_ptr 设为nullptr时 ,会删除包含的对象,并调用析构函数
unique_ptr<Rect> p2 = make_unique<Rect>(2,2);
p2 = nullptr;
当使用move转移所有权时
下面示例中, move() 将p4管理的指针转移给p3,释放p3原来管理的对象,p4包含的对象变为空指针
unique_ptr<Rect> p3 = make_unique<Rect>(3,3);
unique_ptr<Rect> p4 = make_unique<Rect>(4,4);
p3 = move(p4);
使用reset接管新的指针时,会删除原有对象
unique_ptr<Rect> p5 = make_unique<Rect>(5,5);
p5.reset(new Rect(6,6)); //可传入nullptr
unique_ptr 不能进行拷贝和复制
因为unique_ptr对管理的对象具有独占性,因此不能调用拷贝构造函数,也不能进行赋值
unique_ptr<Rect> p1 = make_unique<Rect>(1,1);
unique_ptr<Rect> p2 = p1; // error
unique_ptr<Rect> p1 = make_unique<Rect>(1,1);
unique_ptr<Rect> p2;
p2 = p1; // error
只能使用move 转移所有权
unique_ptr<Rect> p3 = make_unique<Rect>(3,3);
unique_ptr<Rect> p4 = move(p3);
unique_ptr与函数
当函数参数为unique_ptr时,由于不能调用拷贝构造函数,所以不能直接传入
void Draw(unique_ptr<Rect> p)
{
cout<<"画了一个矩形,面积为:"<< p->area() <<endl;
}
int main()
{
unique_ptr<Rect> p1 = make_unique<Rect>(2,4);
//Draw(p1) //error 由于不能调用拷贝构造函数,所以不能直接传入
Draw(move(p1)); //转移所有权,Draw1函数结束时,对象随之释放
//cout<< p1->area()<<endl; //error 所有权被转移,不能使用
return 0;
}
为了解决上面出现的问题,可以使用引用,不转移所有权
void Draw(unique_ptr<Rect>& p)
{
cout<<"画了一个矩形,面积为:"<< p->area() <<endl;
}
int main()
{
unique_ptr<Rect> p1 = make_unique<Rect>(2,4);
Draw(p1);
cout<< p1->area()<<endl;
return 0;
}
或者直接使用原始类型
void Draw1(Rect* p)
{
cout<<"画了一个矩形1,面积为:"<< p->area() <<endl;
}
void Draw2(const Rect& p)
{
cout<<"画了一个矩形2,面积为:"<< p.area() <<endl;
}
int main()
{
unique_ptr<Rect> p1 = make_unique<Rect>(2,4);
Draw1(p1.get());
Draw2(*p1);
cout<< p1->area()<<endl;
return 0;
}
使用建议
unique_ptr仅仅是对指针进行一层包装,所以产生的性能影响很小,但是它可以实现内存的自动管理,unique_ptr很适合在指针数组、容器中使用
shared_ptr
与unique_ptr不同,多个shared_ptr对象可以管理同一个指针,
但是其内部维护了一个引用计数器,引用计数器的值表示管理这个指针的shared_ptr的数量,
当引用计数器归零时,会将被管理的对象销毁,释放内存.
创建
创建方法与unique_ptr相似
shared_ptr<Rect> p1 = make_shared<Rect>(2,4);
shared_ptr<Rect> p2(new Rect(5,6));
常用方法
shared_ptr重载了 * 和 ->运算符,让它可以像普通指针一样使用
//返回unique_ptr管理的原始指针
T* get();
//重载->运算符,内部使用了get()将操作转发给原始指针
T* operator->();
//重载*运算符,内部使用了get()将操作转发给原始指针
T& operator*();
//shared_ptr没有release方法
//T* release();
//删除原有对象,接管新的对象
void reset(T* newObject);
//与其他shared_ptr交换管理的对象
void swap(shared_ptr<T>& other);
//获取引用计数器的值
long use_count();
//返回引用计数是否为1
bool unique();
示例
获取引用计数
shared_ptr<Rect> p1 = make_shared<Rect>(4,6);
shared_ptr<Rect> p2 =p1;
shared_ptr<Rect> p3(p1);
cout<< p1.use_count()<<endl;
类型转换
shared_ptr提供了几个辅助函数,用于对封装指针类型的动态转换、静态转换、常量转换
dynamic_pointer_cast \ static_pointer_cast \ const_pointer_cast
转换失败则返回 nullptr
class Parent
{
public:
virtual ~Parent(){};
void print(){ cout<<"Parent"<<endl;}
};
class Child :public Parent
{
public:
void print(){ cout<<"Child"<<endl;}
};
int main()
{
shared_ptr<Child> p1 = make_shared<Child>();
shared_ptr<Parent> p2 = dynamic_pointer_cast<Parent>(p1);
shared_ptr<Parent> p3 = static_pointer_cast<Parent>(p1);
//由于p2 p3与p1管理的是同一个指针,所以会增加引用计数 输出3
cout<< p1.use_count()<<endl;
return 0;
}
int main()
{
shared_ptr<Parent> p1 = make_shared<Parent>();
shared_ptr<Child> p2 = dynamic_pointer_cast<Child>(p1); //转换失败
shared_ptr<Child> p3 = static_pointer_cast<Child>(p1);
//由于p1本身为Parent类型 不能转成Child类型 转换失败 引用计数为2
cout<< p1.use_count()<<endl;
return 0;
}
int main()
{
shared_ptr<Parent> p1 = make_shared<Child>();
shared_ptr<Child> p2 = dynamic_pointer_cast<Child>(p1);
shared_ptr<Child> p3 = static_pointer_cast<Child>(p1);
//由于p1本身为Child类型 转换成功 引用计数为3
cout<< p1.use_count()<<endl;
return 0;
}
循环引用
当出现循环引用时,引用计数器无法归零,无法释放所指向的资源,造成内存泄漏
class Student
{
public:
Student(const string& name):Name(name){ cout<<"Student构造函数调用"<<endl;}
~Student(){ cout<<"Student析构函数调用"<<endl;}
string Name;
shared_ptr<Student> Deskmate;
};
int main()
{
shared_ptr<Student> s1(new Student("张三"));
shared_ptr<Student> s2(new Student("李四"));
s1->Deskmate = s2;
s2->Deskmate = s1;
//s1和s2产生循环引用 无法释放
return 0;
}
weak_ptr
weak_ptr 需要与shared_ptr结合使用,用来避免循环引用,
weak_ptr 只对shared_ptr管理的对象进行观测,不改变管理对象的引用计数
创建
使用weak+ptr,需要首先创建shared_ptr
shared_ptr<Student> s1(new Student("张三"));
weak_ptr<Student> ws1 = s1;
weak_ptr<Student> ws2(s1);
示例
weak_ptr 可以通过 use_count() 获取引用计数,也可以通过expired() 获取指针是否可用
weak_ptr<Rect> wr;
{
shared_ptr<Rect> r1 = make_shared<Rect>(2,4);
shared_ptr<Rect> r2 = r1;
wr = r1;
cout<<"引用计数:"<< wr.use_count()<<endl; //输出: 2
cout<<"指针是否销毁:"<< wr.expired()<<endl; //输出: 0
}
cout<<"引用计数:"<< wr.use_count()<<endl; //输出: 0
cout<<"指针是否销毁:"<< wr.expired()<<endl; //输出: 1
使用weak_ptr 对前面的示例进行小小的改动 ,就可以解除循环引用
class Student
{
public:
Student(const string& name):Name(name){ cout<<"Student构造函数调用"<<endl;}
~Student(){ cout<<"Student析构函数调用"<<endl;}
string Name;
weak_ptr<Student> Deskmate;
};
int main()
{
shared_ptr<Student> s1(new Student("张三"));
shared_ptr<Student> s2(new Student("李四"));
s1->Deskmate = s2;
s2->Deskmate = s1;
//循环引用解除,正常释放
return 0;
}
通过weak_ptr 获取 shared_ptr
使用 lock() 函数,直接获取对应的shared_ptr,如果获取的结果非空 那么就是可用的
不需要使用 expired() 函数判断
lock() 函数是一个原子操作,其他线程不能读取、修改、中断该操作,因此保证了多线程是获取的shared_ptr是安全的
在多线程环境下使用expired()函数,只有结果为1时才有效,也就是expired()线程不安全
weak_ptr<Rect> wr;
{
shared_ptr<Rect> r1 = make_shared<Rect>(2,4);
shared_ptr<Rect> r2 = r1;
wr = r1;
shared_ptr<Rect> r3 = wr.lock(); //成功 引用计数+1
cout<<"r3 = " <<r3 <<endl; //输出: r3 = 0x1f23f0
}
shared_ptr<Rect> r4 = wr.lock(); //不成功 引用计数不变
cout<<"r4 = " <<r4 <<endl; //输出: r4 = 0
另外可以将weak_ptr作为shared_ptr构造函数的参数,但是如果所观察的对象已经失效
会产生 bad_weak_ptr 异常
weak_ptr<Rect> wr;
{
shared_ptr<Rect> r1 = make_shared<Rect>(2,4);
shared_ptr<Rect> r2 = r1;
wr = r1;
shared_ptr<Rect> r3(wr);
cout<<"r3 = " <<r3 <<endl; //输出: r3 = 0xd623f0
}
try
{
shared_ptr<Rect> r4(wr); //产生异常
cout<<"r4 = " <<r4 <<endl;
}
catch(bad_weak_ptr)
{
cout<<"对象失效"<<endl;
}
实现机制
shared_ptr 和 weak_ptr 内部中既包含控制的对象的指针也包含一个指向控制块的指针
控制块对象 是在shared_ptr第一次接管对象的时候动态创建的
控制块中有两个参数, use_count 和 weak_count
use_count 用于记录当前有多少个shared_ptr在管理整个对象
weak_count 用于记录当前有多少个weak_ptr在观察整个对象
新建 shared_ptr 只会增加 use_count 而不会增加 weak_count
新建 weak_ptr 只会增加 weak_count 而不会增加 use_count
销毁 shared_ptr 会将use_count 减 1
销毁 weak_ptr 会将 weak_count 减 1

当 shared_ptr 离开作用域被销毁 , use_count 引用计数归零时, 会将被管理的对象释放,

此时 use_count 为0,weak_ptr无法继续使用, 当weak_ptr离开作用域后被被销毁,

最后当 use_count与 weak_count都归0时,控制块被销毁
