第五章 RALL机制与智能指针
RAII机制
RAII 是 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。它是 C++ 之父 Bjarne Stroustrup 提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在 RAII 的指导下,C++ 把底层的资源管理问题提升到了对象生命周期管理的更高层次。
当我们 new 出一块内存空间,在使用完之后,如果不使用 delete 来释放这块资源则将导致内存泄露,这在中大型项目中是极具破坏性的。但是人无完人,我们并不能保证每次都记得释放无法再次获取到且不再使用的内存。
为了避免内存申请而由于人为的失误而出现的内存泄漏的问题,C++11把原来Boost库中非常好用的四根智能指针正式转正进入了C++11的标准库中。
C++11给我们程序员提供了四根智能指针,为了帮助程序员们在大中型项目做到资源的自动释放,保证中大型项目的安全,提供了以下四根智能指针:
智能指针并非是一个指针,而是一个实现指针功能的类对象。
智能指针对象是一个定在栈上的类对象:
他是一个类对象,实现了一个指针功能。意义就是这个栈上类对象托管一块堆上的资源。
智能指针的作用
所以这类对象在构造时,托管一块堆上的资源,在析构时,释放堆上的资源。
所以这个智能指针对象所拥有的特性就是栈上对象出栈时,自动销毁。
auto_ptr资源所有权转移智能指针
简介:
auto_ptr
是通过由 new 表达式获得的对象,并在 auto_ptr
自身被销毁时删除该对象的智能指针。它可用于为动态分配的对象提供异常安全、传递动态分配对象的所有权给函数和从函数返回动态分配的对象。
复制 auto_ptr
,会复制指针并转移所有权给目标: auto_ptr
的复制构造和复制赋值都会修改其右侧参数,而且“副本”不等于原值。因为这些不常见的复制语义,不可将 auto_ptr
置于标准容器中。
封装能够实现指针功能的类对象源码
#include <iostream>
#include <memory>
using namespace std;
class Stu
{
private:
int _age;
string _name;
public:
Stu(int age,string name)
{
this->_age=age;
this->_name=name;
cout << "Stu structure" << endl;
}
void showInfo()
{
cout << "姓名:" << this->_name << ",年龄:" << this->_age << endl;
}
~Stu()
{
cout << "Stu destruct" << endl;
}
};
template<class T>
class Auto_ptr
{
private:
T* _ptr;
public:
Auto_ptr(T* ptr=nullptr)
{
this->_ptr=ptr;
cout << "Auto_ptr structure" << endl;
}
Auto_ptr(Auto_ptr& other)
{
this->_ptr=other._ptr;
other._ptr=nullptr;
cout << "Auto_ptr copy_structure" << endl;
}
Auto_ptr& operator=(Auto_ptr& other)
{
cout << "Auto_ptr operator= function" << endl;
if(this==&other)
{
return *this;
}
if(nullptr!=other._ptr)
{
delete this->_ptr;
this->_ptr=other._ptr;
other._ptr=nullptr;
}
}
T* operator->()
{
cout << "Auto_ptr operator-> function" << endl;
return this->_ptr;
}
T* get_ptr()
{
cout << "Auto_ptr get_ptr_function " << endl;
return this->_ptr;
}
~Auto_ptr()
{
if(nullptr!=this->_ptr)
delete this->_ptr;
cout << "Auto_ptr destruct" << endl;
}
};
int main()
{
//在堆上开辟对象空间(需要手动释放)
Stu* stu=new Stu(19,"Tom");
stu->showInfo();
delete stu;
//通过指针模板类,创建对象,用于验证get_ptr()函数
Auto_ptr<Stu> s1(new Stu(20,"DaMing"));
//通过get_ptr函数获取属性指针,访问抽象类对象中的成员函数
s1.get_ptr()->showInfo();
//指针模板类的拷贝构造,此刻s1所有资源转移
Auto_ptr<Stu> s2=s1;
//指针模板类中的->运算符重载函数
s2->showInfo();
//通过指针模板类,创建对象
Auto_ptr<Stu> s3(new Stu(15,"LiLei"));
s3->showInfo();//指针模板类中的->运算符重载函数
s3=s2;//指针模板类中的=运算符重载函数,此刻s2所有资源转移
s3->showInfo();//指针模板类中的->运算符重载函数
//调用头文件#include<memory>下的
//auto_ptr智能指针(在c++17弃用)
auto_ptr<Stu> s4(new Stu(18,"XiaoMei"));
s4->showInfo();
return 0;
}
运行结果分析图
独占智能指针unique_ptr
简介:
unique_ptr智能指针,一个unique_ptr指针只管理一个堆上资源,不发生拷贝构造,也不会等号运算符重载。
unique,英译:独一无二的,唯一的智能指针,他是不共享的,他不像其它的共享对象,也操作他的指向的堆区空间,他是唯一一个可操作这个堆区空间的对象。也就是说他的类对象是不可以发生拷贝构造与赋值运算符重载的。
简单使用方法
#include <iostream>
#include <memory>
using namespace std;
class Stu
{
private:
int _age;
string _name;
public:
Stu(int age,string name)
{
this->_age=age;
this->_name=name;
cout << "Stu structure" << endl;
}
void showInfo()
{
cout << "姓名:" << this->_name << ",年龄:" << this->_age << endl;
}
~Stu()
{
cout << "Stu destruct" << endl;
}
};
int main()
{
unique_ptr<Stu> u1(new Stu(19,"DaMing"));
u1.get()->showInfo();
return 0;
}
运行结果分析图
问题
如何实现内部实现?
- 把这个拷贝构造与赋值运算符函数设为私有。
- 删除类对象中的成员函数。 //系统给我提供了一个关键字可以把他给删除了。在函数名后面加上 = delete;
unique_ptr(const unique_ptr& ptr) = delete;
void operator=(unique_ptr& ptr) = delete;
shared_ptr共享智能指针
简介:
shared_ptr共享智能指针是可以由多个栈上的智能指针对象同时托管一块堆资源
shared_ptr内部实现:堆上引用计数器,计数器个数为0时,开始释放内存空间
#include <iostream>
#include <memory>
using namespace std;
class Stu{
private:
string name;
int age;
public:
Stu(string name,int age){
this->age=age;
this->name=name;
}
void showInfo(){
cout << "姓名:" << this->name << ",年龄:" << this->age << endl;
}
~Stu(){
cout << "stu destruct" << endl;
}
};
//实现一个Shared_ptr的引用计数
template <class T>
class RefCount{
private:
T* ptr;
int count;
public:
RefCount(T* ptr=nullptr){
if(ptr!=nullptr){
this->count=1;
}else{
this->count=0;
}
}
//增加引用计数
void addRef(){
this->count++;
}
//减少引用计数
int delRef(){
return --count;
}
int get_count(){
return this->count;
}
};
template <class T>
class Shared_ptr{
private:
T* ptr;
RefCount<T>* refcount;
public:
//构造函数
Shared_ptr(T* ptr=nullptr){
this->ptr=ptr;
if(this->ptr!=nullptr){
this->refcount=new RefCount<T>(ptr);
}else {
this->refcount=new RefCount<T>();
}
}
//析构函数
~Shared_ptr(){
if(refcount->delRef()==0){
delete this->ptr;
this->ptr=nullptr;
refcount=nullptr;
}
}
//拷贝构造
Shared_ptr(const Shared_ptr& other){
if(other.ptr!=nullptr){
this->ptr=other.ptr;
this->refcount=other.refcount;
refcount->addRef();
}else {
this->ptr=other.ptr;
this->refcount=other.refcount;
}
}
//等号运算符重载
Shared_ptr& operator=(const Shared_ptr& other){
if(this==&other){
return this;
}
if(other.ptr!=nullptr){
if(refcount->delRef()==0){
delete this->ptr;
delete refcount;
this->ptr=nullptr;
refcount=nullptr;
}
this->ptr=other.ptr;
this->refcount=other.refcount;
refcount->addRef();
}else{
if(refcount->delRef()==0){
delete this->ptr;
delete refcount;
this->ptr=nullptr;
refcount=nullptr;
}
this->ptr=other.ptr;
this->refcount=other.refcount;
}
}
T& operator*(){
return *ptr;
}
T* operator->(){
return ptr;
}
T* get(){
return ptr;
}
int use_count(){
return refcount->get_count();
}
};
int main()
{
Stu *pstu=new Stu("yao",19);
pstu->showInfo();
delete pstu;
shared_ptr<Stu> p(new Stu("liang",20));
p->showInfo();
shared_ptr<Stu> p1=p;//是拷贝
p->showInfo();
p1->showInfo();
Shared_ptr<Stu> p2(new Stu("minmin",17));
p2->showInfo();
Shared_ptr<Stu> p3=p2;
p3->showInfo();
return 0;
}
交叉引用图:
交叉引用:
#include <iostream>
#include <memory>
using namespace std;
class B;
class A{
public:
shared_ptr<B> ptr_b;
A(){
cout << "A structure" << endl;
}
~A(){
cout << "A destruct" << endl;
}
//a中的槽函数
void slot_funtions(){
cout << "加油!" << endl;
}
};
class B{
public:
shared_ptr<A> ptr_a;
B(){
cout << "B structure" << endl;
}
~B(){
cout << "B destruct" << endl;
}
void signal(){
ptr_a->slot_funtions();
}
};
int main()
{
shared_ptr<A> p_A(new A());
shared_ptr<B> p_B(new B());
p_A->ptr_b=p_B;//等号运算符重载
p_B->ptr_a=p_A;
p_B->signal();
return 0;
}
存在 问题 :资源泄露,原因引用计数为2,return 时为1,不能析构
解决:std::weak_ptr 的另一用法是打断 std::shared_ptr 所管理的对象组成的环状引用。若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr
引用计数无法抵达零,而内存被泄露。能令环中的指针之一为弱指针以避免此情况。
weak_ptr破环指针
#include <iostream>
#include <memory>
using namespace std;
class B;
class A{
public:
weak_ptr<B> ptr_b;
A(){
cout << "A structure" << endl;
}
~A(){
cout << "A destruct" << endl;
}
//a中的槽函数
void slot_funtions(){
cout << "加油!" << endl;
}
};
class B{
public:
weak_ptr<A> ptr_a;
B(){
cout << "B structure" << endl;
}
~B(){
cout << "B destruct" << endl;
}
void signal(){
shared_ptr<A> temp=ptr_a.lock();
if(nullptr!=temp)
temp->slot_funtions();
}
};
int main()
{
shared_ptr<A> p_A(new A());
shared_ptr<B> p_B(new B());
p_A->ptr_b=p_B;
p_B->ptr_a=p_A;
p_B->signal();
return 0;
}
强弱智能指针的使用时机:
当智能指针对象直接托管堆上资源时,就是强引用智能指针。
当智能指针对象之间互相引用或赋值时,就使用弱引用智能指针,因为弱引用智能指针,不改变资源的引用记数,不操控堆上资源的属性与方法,他只是一个资源的观察者。这样可以避免堆上资源因交叉引用而出现的资源无法释放的问题。
所以weak_ptr也是shared_ptr的一个黄金搭档。作为强引用智能指针有力补充。
智能指针的问题与解决方案:删除器(Deletor)
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
class A
{
public:
A()
{
cout << "A的构造" << endl;
}
~A()
{
cout << "A的析构" << endl;
}
void showInfo()
{
cout << "hello" << endl;
}
};
//使用函数对象作为自定删除器:
template <class T>
class Deletor
{
public:
void operator()(T* p)
{
delete []p;
}
};
template <class T>
void deletor(T* p)//void (*)(T* p)
{
delete []p;
}
int main()
{
// 1.使用默认删除器,来释放智能指针所托管的堆上连续多个空间的方式。
// unique_ptr<A,default_delete<A[]>> ptr1(new A[5]);
// 2.使用函数对象作为自定义删除器:
// unique_ptr<A,Deletor<A>> ptr2(new A[5]);
// ptr2->showInfo();
// 3.使用函数指针作为自定义删除器:
// unique_ptr<A,void (*)(A*)> ptr3(new A[5],deletor<A>);
// ptr3->showInfo();
// 4.使用包装器包装一个函数的调用对象,作为删除器对象。
// unique_ptr<A,function<void (A*)>> ptr4(new A[5],deletor<A>);
// ptr4->showInfo();
// 5.使用包装器包装一个lambda表达式,作为自定义删除器。
// unique_ptr<A,function<void (A*)>> ptr5(new A[5],[](A* p){ delete []p;});
// ptr5->showInfo();
// shared_ptr<A> ptr(new A[5],default_delete<A[]>());
// ptr->showInfo();
// shared_ptr<A> ptr(new A[5],Deletor<A>());
// ptr->showInfo();
// shared_ptr<A> ptr(new A[5],deletor<A>);
// ptr->showInfo();
shared_ptr<A> ptr(new A[5],[](A* p){ delete []p;});
ptr->showInfo();
return 0;
}