C++学习之第十三天-智能指针

知识点总结:

1.引出: 如何智能指针自动关闭文件指针-代码实现

2.通用智能指针进行资源管理RAII-重载解引用运算符*和 -> 运算符,用于托管new出来的对象的释放。---代码实现

3.C++四种智能指针:auto_ptr, unique_ptr, shared_ptr,weak_ptr.

        1.auto_ptr有bug,已被弃用

        2.unique_ptr-不会让多个对象拥有同一资源,不允许进行拷贝构造、赋值,支持移动语义

        3.shared_ptr-强引用,允许多个指针指向同一对象,写时复制思想,支持移动语义。

        4.weak_ptr弱引用,不能拥有资源,只能绑定到shared_ptr指向的对象,不会改变其引用计数.-lock(),expired.

4.为智能指针定制删除器:

        1.用类仿函数技术来重写删除器       

        2.unique_ptr删除器在模板第二个参数

扫描二维码关注公众号,回复: 13289782 查看本文章

        3.shared_ptr删除器在构造函数第二个参数中,且接收的是类的对象。

一.智能指针自动关闭文件指针代码实现

#include <iostream>
using namespace std;

class SafeFile
{
public:
    SafeFile(FILE *fp)
    :_fp(fp)
    {
        cout<<"SafeFile(FILE *)"<<endl;
        if(nullptr==_fp)
        {
            cout<<"nullptr==_fp"<<endl;
        }
    }

    void write(const string &msg)
    {
        fwrite(msg.c_str(),1,msg.size(),_fp);
    }

    ~SafeFile()
    {
        cout<<"~SafeFile()"<<endl;
        if(_fp)
        {
            fclose(_fp);
            cout<<"fclose(_fp)"<<endl;
        }
    }
private:
    FILE *_fp;
};
void test()
{
    string s1 = "hello world\n";
    SafeFile sf(fopen("text.txt","a+"));
    sf.write(s1);
}
int main()
{
    test();
    return 0;
}
/*
运行结果:
SafeFile(FILE *)
~SafeFile()
fclose(_fp)

*/

二.用智能指针进行资源管理RAII-Resource Acquisition Is Initialization

1.在构造的时候初始化资源,或者在托管已经构造好的资源
2.在析构的时候释放资源
3.不允许复制或者赋值
4.提供若干个访问资源的方法
RAII的本质:用栈对象的生命周期来管理资源。

#include <iostream>

using namespace std;
class Person;
template<typename T>//通用指针管理模板
class RAII
{
public:
    //1.在对象构建的时候初始化资源
    RAII(T *data)//初始化的时候传入T类型的指针,用_data接收
    :_data(data)//data是T类型的指针
    {
        cout<<"RAII()"<<endl;
    }

    //2.提供若干个访问资源的方法
    T *operator->()//重写->运算符函数来对_data进行访问
    {
        return _data;
    }

    T& operator*()//重写*运算符
    {
        return *_data;
    }
    T *get()
    {
        return _data;
    }

    void reset(T *data)
    {
        if(_data)
        {
            delete _data;
            _data = nullptr;
        }
        _data = data;
    }
    //3.不允许拷贝构造与赋值操作
    RAII(const RAII &)=delete;//删掉拷贝构造
    RAII &operator=(const RAII &rhs)=delete;

    //4.在对象销毁的时候释放资源
    ~RAII()
    {
        cout<<"~RAII()"<<endl;
        if(_data)
        {
            delete _data;
            _data = nullptr;
        }
    }
private:
    T *_data;
};

class Person//Person自定义类
{
public:
    Person(int age)
    {
        cout<<"Person(int)"<<endl;
        m_Age = age;
    }
    void showAge()
    {
        cout<<"Age:"<<m_Age<<endl;
    }

    ~Person()
    {
        cout<<"~Person()"<<endl;
    }
    int m_Age;
};
void test01()
{
    RAII<class Person> r1(new Person(19));//r1智能指针,管理new出来的堆对象,程序执行结束,自动释放
    r1->showAge();

    (*r1).showAge();
}
void test02()
{
    RAII<int> raii(new int(10));//
    cout<<*raii.get()<<endl;
    cout<<(*raii)<<endl;
}
int main()
{
    //test01();
    test02();
    return 0;
}

test01运行结果

Person(int)
RAII()
Age:19
Age:19
~RAII()
~Person()

test02()运行结果

RAII()
10
10
~RAII()

三.C++四种智能指针

1.auto_ptr

#include <iostream>
#include <memory>
using namespace std;

void test01()
{
    int *pInt = new int(10);
    std::auto_ptr<int> ap(pInt);
    cout<<"*ap = "<<*ap<<endl;
    
    cout<<"*pInt = "<<pInt<<endl;
    cout<<"ap.get() = "<<ap.get()<<endl;


    auto_ptr<int> ap2(ap);
    cout<<"*ap2="<<*ap2<<endl;
    
    cout<<"*ap = "<<*ap<<endl;//此时ap是个空指针,底层把ap释放了,auto_ptr的问题所在
    //底层已经发生了所有权的转移,所以该智能指针存在bug

}
int main()
{
    test01();
    return 0;
}

2.unique_ptr

1.不允许进行拷贝构造,不允许赋值,不会让多个对象拥有同一资源

2.支持移动语义,能把右值的资源转移给左值

3.由于 vector<unique_ptr<int>>number是存储智能指针的容器,在往容器插入的时候,会调用unique_ptr<int>的拷贝构造函数,而unique_ptr是不支持拷贝构造的,所以插入前,要把智能指针转为右值再进行插入,调用移动构造函数,把资源的管理权交给容器。

4.智能指针通过move转为右值后,不再支持使用。

5.unique_ptr<int> up4(new int(30))与 vector<int *>vec的区别,前者通过智能指针自动把资源给释放掉;后者释放的话要手动释放。

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

void test01()
{
    unique_ptr<int> up(new int(10));

    cout<<"*up="<<*up<<endl;
    cout<<"up.get()="<<up.get()<<endl;
    cout<<endl;

    //unique_ptr<int>up2(up);//会报错,不会让多个对象拥有同一资源
    //不允许进行拷贝构造,也不允许赋值
    unique_ptr<int> up3(new int(20));
    //up3 = up1;//不允许进行赋值
    
    cout<<endl;
    unique_ptr<int> up4(new int(30));
    vector<unique_ptr<int>>number;
    //number.push_back(up3);不允许进行拷贝构造,因此会报错
    //number.push_back(up4);
    number.push_back(move(up3));//把up3从左值转为右值后,push_back后不会再进行拷贝构造函数
    //而去调用移动语义函数
    number.push_back(move(up4));//unique_ptr允许移动语义,
    //或者直接传一个右值进来,会调用移动构造函数,把up4管理的资源交给容器
    number.push_back(unique_ptr<int>(new int(50)));
    for(vector<unique_ptr<int>>::iterator it=number.begin();it!=number.end();it++)
    {
        cout<<*(*it)<<endl;
    }
    
    cout<<endl;
    vector<int *>vec;//和上面有智能指针的区别,这里new出来的堆空间没有被释放
    vec.push_back(new int(10));


}
int main()
{
    test01();
    return 0;
}

运行结果

*up=10
up.get()=0x5634f9655e70


*up3=20
20
30
50

3.shared_ptr

1.        

        shared_ptr智能指针使用的是写时赋值思想(COW),使用浅拷贝和引用计数,use_count函数记录指向一块内存的指针的数目。

        shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数变为0,其析构函数便会销毁对象,并且释放它所占用的内存。

2.shared_ptr允许多个指针指向同一对象,解引用一个智能指针返回它指向的对象

3.shared_ptr智能指针支持移动语义。

4.shared_ptr存在的问题:循环引用的时候会出现内存泄露的问题

test01测试:

1.多个指针指向同一空间

2.支持拷贝构造-进行浅拷贝,引用计数+1

3.允许进行赋值操作--释放左操作,进行浅拷贝,引用计数+1

void test01()
{
    //测试1:允许拷贝构造,允许多个指针指向同一空间
    shared_ptr<int> sp(new int(10));
    cout<<"sp="<<sp<<endl;
    cout<<"*sp="<<*sp<<endl;
    cout<<"sp.get()="<<sp.get()<<endl;//获取智能指针的地址
    cout<<"sp.use_count()="<<sp.use_count()<<endl;//引用计数
 
    shared_ptr<int> sp2(sp);//拷贝构造,允许指向同一对象
    cout<<endl;
    cout<<"sp2="<<sp2<<endl;//地址没变
    cout<<"*sp2="<<*sp2<<endl;
    cout<<"sp2.use_count()="<<sp2.use_count()<<endl;//引用计数增加了
    
    //测试2:允许进行赋值操作
    shared_ptr<int> sp3(new int(20));
    cout<<"sp3="<<sp3<<endl;
    sp3 = sp; //3.赋值操作,sp3之前托管的资源会被自动释放掉。
    cout<<"进行赋值操作后:sp3="<<sp3<<endl//智能指针地址变量
        <<"sp="<<sp<<endl;
    cout<<"sp3 = "<<*sp3<<endl;
    cout<<"sp3.use_count()="<<sp3.use_count()<<endl;//由2变成3

    
}

运行结果

sp=0x55d0ff160e70
*sp=10
sp.get()=0x55d0ff160e70
sp.use_count()=1

sp2=0x55d0ff160e70
*sp2=10
sp2.use_count()=2

sp3=0x55901e9e62c0    //进行赋值操作前的地址
sp3=0x55901e9e5e70    //赋值操作后,地址变了
sp=0x55901e9e5e70    //sp=sp3了
sp3 = 10
sp3.use_count()=3    //引用计数由2变成3了

test02:shared_ptr支持移动语义。

void test02()
{

    shared_ptr<int> sp4(new int(30));
    vector<shared_ptr<int>> number;
    //也具有移动语义的特点
    number.push_back(sp4);//左值ok,因为支持拷贝构造
    number.push_back(move(sp4));//右值ok,支持移动语义,sp4转为右值
    number.push_back(shared_ptr<int>(new int(100)));//临时右值ok
    for(vector<shared_ptr<int>>::iterator lt = number.begin();lt!=number.end();lt++)
    {
        cout<<*(*lt)<<endl;
        cout<<"use_count()="<<(*lt).use_count()<<endl;
    }
}

运行结果:

30
use_count()=2
30
use_count()=2
100
use_count()=1

shared_ptr出现的问题:循环引用---用weak_ptr可以解决shared_ptr循环引用的问题

#include <iostream>
using namespace std;
#include <memory>

class Child;
class Parent
{
public:
    Parent()
    {
        cout<<"Parent()"<<endl;
    }


    ~Parent()
    {
        cout<<"~Parent()"<<endl;
    }

    shared_ptr<Child> pParent;
};

class Child
{
public:
    Child()
    {
        cout<<"Child()"<<endl;
    }


    ~Child()
    {
        cout<<"~Child()"<<endl;
    }

    shared_ptr<Parent> pChild;//循环引用
};
void test01()
{
    shared_ptr<Parent> ptrParent(new Parent());//智能指针(栈对象)

    shared_ptr<Child> ptrChild(new Child());

    cout<<"ptrParent.use_count()="<<ptrParent.use_count()<<endl;
    cout<<"ptrChild.use_count()="<<ptrChild.use_count()<<endl;

    //ptrParent->pParent是个Child类型的被托管的指针
    ptrParent->pParent = ptrChild;
    ptrChild->pChild = ptrParent;//这里进行了相互循环的指引
    cout<<"ptrParent.use_count()="<<ptrParent.use_count()<<endl;//2
    cout<<"ptrChild.use_count()="<<ptrChild.use_count()<<endl;//2
    //程序结束后,栈对象被销毁,引用计数都变成了1,造成了内存泄露,这是问题所在



}
int main()
{
    test01();
    return 0;
}

4.weak_ptr

1.shared_ptr是个强引用,weak_ptr是一个弱引用,指向shared_ptr所管理的对象。

2.将一个weak_ptr绑定到一个shared_ptr不会改变其引用计数。

3.weak_ptr不能获得资源,必须通过lock()函数从wp提升为sp,从而判断共享的资源是否已经销毁。

-如果wp.lock()为空(0),说明weak_ptr对象所托管的资源已被销毁;

-wp.lock()返回一个指向共享对象的shared_ptr对象

test01:

1.weak_ptr不能获取资源,只能绑定到shared_ptr对象上,只有无参构造函数

2.绑定到shread_ptr上,引用计数不会增加

3.没有重载解引用运算符和->运算符,通过lock()把weak_ptr提升为shared_ptr.

void test01()
{
    // weak_ptr<Point>wp(new Point(1,2));不能直接创建
    weak_ptr<Point>wp;
    shared_ptr<Point>sp(new Point(1,2));
    wp = sp;//调用weak_ptr的赋值运算符函数
    //对weak_ptr的对象进行赋值操作,引用计数不会进行++;
    sp->print();
    cout<<"wp.use_count()="<<wp.use_count()<<endl;
    cout<<"sp.use_count()="<<sp.use_count()<<endl;

    wp.lock()->print();
   // //wp->print();对于wp而言,没有重载解引用运算符和->运算符
   // shared_ptr<Point> sp2 = wp.lock();//把weak_ptr转为shared_ptr
   // sp2->print();//还能打印的话说明提升成功,托管的资源未消失
   // cout<<"sp2.use_count()="<<sp2.use_count()<<endl;//引用计数增加了
   cout<<sp.use_count()<<endl;
}

运行结果

Point(int =0,int = 0)
_ix=1    //    sp->print();
_iy=2
wp.use_count()=1    
sp.use_count()=1
_ix=1    // wp.lock()->print();
_iy=2
1        // cout<<sp.use_count()<<endl;
~Point()

test02():

1.wp.expired()的值为1,说明weak_ptr所托管的shared_ptr资源已被销毁

void test02()
{

    weak_ptr<Point>wp;
    {//加个大括号,局部对象

        shared_ptr<Point>sp(new Point(1,2));
        wp = sp;//调用weak_ptr的赋值运算符函数
        //对weak_ptr的对象进行赋值操作,引用计数不会进行++;
        cout<<"wp.expired()="<<wp.expired()<<endl;
        sp->print();
        cout<<"wp.use_count()="<<wp.use_count()<<endl;
        cout<<"sp.use_count()="<<sp.use_count()<<endl;

        //wp->print();对于wp而言,没有重载解引用运算符和->运算符
        shared_ptr<Point> sp2 = wp.lock();//把weak_ptr提升为shared_ptr
        if(sp2)
        {
            cout<<"提升成"<<endl;
            cout<<"*sp2=";
            sp2->print();
            cout<<"sp2.use_count()="<<sp2.use_count()<<endl;//引用计数增加了
        }
        else
        {
            cout<<"提升失败,托管的资源已被回收."<<endl;
        }
    }
    cout<<"wp.expired()="<<wp.expired()<<endl;//值为1,说明托管的资源已被销毁
    shared_ptr<Point> sp2 = wp.lock();//把weak_ptr转为shared_ptr
    if(sp2)
    {
        cout<<"提升成"<<endl;
        cout<<"*sp2=";
        sp2->print();
    }
    else
    {
        cout<<"提升失败,托管的资源已被回收."<<endl;
    }

}

运行结果

Point(int =0,int = 0)
wp.expired()=0
_ix=1
_iy=2
wp.use_count()=1
sp.use_count()=1
提升成
*sp2=_ix=1
_iy=2
sp2.use_count()=2
~Point()
wp.expired()=1     //值等于1,说明weak_ptr托管的对象已被销毁
提升失败,托管的资源已被回收.
 

四、为智能指针定制删除器

        很多时候我们都用new来申请空间,用delete来释放。库中实现的各种智能指针,默认也都是用delete来释放空间。

        但是若我们采用malloc申请的空间或是用fopen打开的文件,这时我们的智能指针就无法来处理。 因此我们需要为智能指针定制删除器,提供一个可以自由选择析构的接口,这样,我们的智能指针就可以处理不同形式开辟的空间以及可以管理文件指针。

1.unique_ptr<FILE,FILEClose>--unique_ptr的删除器定义在模板里

2.shared_ptr<FILE> sp(fopen("shared_ptr.txt","a+"),FILEClose())

--shared_ptr的删除器在构造函数里,且接收的是一个类的对象

3.仿函数技术----给类对象加个括号或者能给对象进行传参就能实现一个功能,本质上是重载()运算符。

#include <iostream>
using namespace std;
#include <memory>
#include <string>
class  FILEClose//仿函数技术
{
public:
    void operator()(FILE *fp)const
    {
        if(nullptr !=fp)
        {
            fclose(fp);
            cout<<"fclose(fp)"<<endl;
        }
    }
};
void test01()
{
    string msg = "hello,world\n"; 
    unique_ptr<FILE,FILEClose> up(fopen("wd.txt","a+"));
    //对于文件指针,不能使用默认的删除器,要自己的回调函数来进行管理
    fwrite(msg.c_str(),1,msg.size(),up.get()); 

    //fclose(up.get());//如果没有fclose(),数据是写在缓冲区里面,写不到文件里
}
//test01()中如果没自定义删除器,就不会把hello,world写入到wd.txt里面,
//对于文件指针而言,文件指针只能被close掉,而不能删掉,用智能指针来管理的话
//里面有个删除器,在程序执行结束,就会把文件指针删掉,没有检测文件指针被close,就无法刷新缓冲区。
//数据留在了缓冲区里
void test02()
{
    string msg = "hello,world\n";
    //shared_ptr的删除器在构造函数里,且接收的是一个类的对象
    //FILEClose tmp;
    shared_ptr<FILE> sp(fopen("shared_ptr.txt","a+"),FILEClose());
    fwrite(msg.c_str(),1,msg.size(),sp.get());

    //fclose(sp.get());

}
int main()
{
    //test01();
    test02();
    return 0;
}

五.智能指针的滥用。

        本质:同一个裸指针被不同的智能指针托管,导致被析构两次。

void test01()
{
    Point *pt = new Point(1,2);
    unique_ptr<Point> up1(pt);
    unique_ptr<Point> up2(pt);
    //pt用了两个不同的智能指针来托管了,发生重释放
}
void test02()
{
    //将同一个裸指针用不同的智能指针进行托管
    unique_ptr<Point> up1(new Point(1,2));
    unique_ptr<Point> up2(new Point(3,4));
    up1.reset(up2.get());
}
void test03()
{
    Point *pt = new Point(1,2);
    shared_ptr<Point> up1(pt);
    //shared_ptr<Point> up2(pt);Error
    //pt用了两个不同的智能指针来托管了,发生重释放
    
    shared_ptr<Point> up2(up1);//正常
}
#include <iostream>
#include <memory>
using namespace std;
class Point:public enable_shared_from_this<Point>
{
public:
    Point(int ix=0, int iy = 0)
    :_ix(ix),_iy(iy)
    {
        cout<<"Point()"<<endl;
    }
    void print()
    {
        cout<<"ix="<<_ix<<endl
            <<"iy="<<_iy<<endl;
    }

    //Point *add(Point *pt)
    shared_ptr<Point> add(Point *pt)
    {
        _ix += pt->_ix;
        _iy += pt->_iy;
        //return this;
        //return shared_ptr<Point>(this);这会导致临时对象和sp1本身同时托管同一片堆区
        return shared_from_this();//返回一个shared_ptr类型,不会进行拷贝构造
    }
    ~Point()
    {
        cout<<"~Point()"<<endl;
    }

private:
    int _ix;
    int _iy;
};


int main()
{
    //test01();
    //test02();
    //test03();
    test04();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_49278191/article/details/121296735