C++智能指针

C++智能指针

智能指针介绍

  1. 内存泄漏
  • 堆内存泄漏:在堆上申请资源,在结束使用的时候没有释放归还给OS,导致该块内存不会被再次使用

  • 资源泄露:系统资源如socket、文件描述符等,这些在系统中是有限制的,如果创建了不归还就会耗尽资源,导致其他程序不可用

  1. 智能指针
  • C#和Java中都有自动垃圾回收机制,GC可以管理分配的堆内存,在对象失去引用时自动回收,因此,在C#和Java中内存管理不是问题

  • C++语言没有垃圾回收机制,必须自己去释放动态分配的堆内存

  • 解决这个问题最有效的方法是使用智能指针智能指针,在智能指针对象中有一个指针,此指针存储的是动态创建对象的地址,用于生存期控制,能够确保智能指针对象离开所在作用域时,自动正确地销毁动态创建的对象,防止内存泄漏

  1. RAII
  • RAII:Resource Acquisition Is Initialiaztion,资源获取即初始化,使用局部对象来管理资源的技术

  • RAII充分利用了C++语言局部对象自动销毁的特性来控制资源的生命周期,资源主要是指操作系统中有限的如内存heap、网络套接字、互斥量、文件句柄等,局部对象存储在栈中的对象,其生命周期由操作系统管理

  • RAII过程总结:

    1. 设计一个类封装资源

    2. 在构造函数中初始化

    3. 在析构函数中执行销毁操作

    4. 使用时定义一个该类的局部对象

实现一个简单的智能指针

  1. 构造
template<typename T>
class mySmartPoint{
public:
    mySmartPoint(T *ptr = nullptr)
    : _ptr(ptr) {}
    ~mySmartPoint() { delete _ptr; }
private:
    T *_ptr;
};
int main(){
    //栈上的对象出作用域自动析构对象
    mySmartPoint<int> point1(new int);
    
    return 0;
}
  1. 添加指针操作符重载函数
template<typename T>
class mySmartPoint{
public:
    mySmartPoint(T *ptr = nullptr)
    : _ptr(ptr) {}
    ~mySmartPoint() { delete _ptr; }
    T& operator*()  { return *_ptr;}
    T* operator->()  { return _ptr;}
private:
    T *_ptr;
};

不带引用计数的智能指针

  • 不带引用计数的智能指针推荐使用unique_ptr
    //unique_ptr(unique_ptr<T> &&src)
    //unique_ptr<T>& operator=(unique_ptr<T> &&src)
    unique_ptr<int> p1(new int);
    unique_ptr<int> p2(std::move(p1));//std::move得到当前变量的右值类型

带引用计数的智能指针

  • 带引用计数的智能指针share_ptr和weak_ptr

  • 多个智能指针可以管理同一个资源

  • 给每个对象资源匹配一个引用计数,有资源的时候引用计数加1,不使用资源的时候引用计数减1,引用计数为0时释放资源

template<typename T>
class RefCnt{
public:
    RefCnt(T *ptr = nullptr) : _ptr(ptr){
        if(_ptr != nullptr) { _cnt = 1;}
    }
    ~RefCnt(){ delete _ptr; _ptr = nullptr; }
    void addRefCnt(){
        _cnt++;//添加引用计数
    }
    int delRefCnt(){
        return --_cnt;//减少引用计数
    }
private:
    T* _ptr;
    int _cnt;
};

template<typename T>
class mySmartPoint{
public:
    mySmartPoint(T *ptr = nullptr) : _ptr(ptr) {
        _pRefCnt = new RefCnt<T>(_ptr);
    }
    ~mySmartPoint(){
        if(0 == _pRefCnt->delRefCnt()){
            delete _ptr;
            _ptr = nullptr;
        }
    }
    T& operator*()  { return *_ptr;}
    T* operator->()  { return _ptr;}
    mySmartPoint(const mySmartPoint<T> &src)
        : _ptr(src._ptr), _pRefCnt(src._pRefCnt){
        if(_ptr != nullptr)
            _pRefCnt->addRefCnt();
    }
    mySmartPoint<T>& operator=(const mySmartPoint<T> &src){
        if(this == &src)
            return *this;
        if(0 == _pRefCnt->delRefCnt())
            delete _ptr;
        _ptr = src._ptr;
        _pRefCnt = src._pRefCnt;
        _pRefCnt->addRefCnt();
        return *this;
    };
private:
    T *_ptr;//指向资源的指针
    RefCnt<T> *_pRefCnt;//指向该资源引用计数对象的指针
};
int main(){
    mySmartPoint<int> p1(new int);
    mySmartPoint<int> p2(p1);
    mySmartPoint<int> p3;
    p3 = p2;
}

shared_ptr的交叉引用问题

  • shared_ptr强智能指针,可以改变资源的引用计数

  • weak_ptr弱智能指针,不会改变资源的引用计数,观察资源,没有提供*和->运算符重载函数

  • 强智能指针的交叉引用问题:

class B;
class A{
public:
    A() {cout << "A()" << endl;}
    ~A(){cout << "~A()" << endl;}
    shared_ptr<B> _ptrB;
};
class B{
public:
    B() {cout << "B()" << endl;}
    ~B(){cout << "~B()" << endl;}
    shared_ptr<A> _ptrA;
};

int main(){
    shared_ptr<A> pA(new A());
    shared_ptr<B> pB(new B());
    pA->_ptrB = pB;
    pB->_ptrA = pA;
    cout << pA.use_count() << endl;//2
    cout << pB.use_count() << endl;//2
    return 0;
}
  • 解决方法:定义对象的时候用强智能指针;引用对象的地方用弱智能指针
class B;
class A{
public:
    A() {cout << "A()" << endl;}
    ~A(){cout << "~A()" << endl;}
    weak_ptr<B> _ptrB;
};
class B{
public:
    B() {cout << "B()" << endl;}
    ~B(){cout << "~B()" << endl;}
    weak_ptr<A> _ptrA;
};

int main(){
    shared_ptr<A> pA(new A());
    shared_ptr<B> pB(new B());
    pA->_ptrB = pB;
    pB->_ptrA = pA;
    cout << pA.use_count() << endl;//1
    cout << pB.use_count() << endl;//1
    
    return 0;
}
  • 应用:多线程访问共享对象的线程安全问题
class A{
public:
    A() {cout << "A()" << endl;}
    ~A(){cout << "~A()" << endl;}
    void showA(){ cout << "showA()" << endl;}
    //weak_ptr<B> _ptrB;
};
//子线程
void handle01(weak_ptr<A> pw){
    //std::this_thread::sleep_for(std::chrono::seconds(2));
    //q访问A对象的时候,需要侦测A对象是否存活
    shared_ptr<A> sp = pw.lock();
    if( sp != nullptr){
        sp->showA();
    }
    else{
        cout << "A对象被析构了不能访问" << endl;
    }
}
//main线程
int main(){
    {
        shared_ptr<A> p(new A());
        thread t1(handle01, weak_ptr<A> (p));
        t1.detach();
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
    //阻塞等待子线程结束
    std::this_thread::sleep_for(std::chrono::seconds(20));
    
    return 0;
}

智能指针的删除器

  • unique_ptr的析构函数是一个函数对象的调用deletor(ptr)

  • 默认的deletor定义

template<typename T>
class Deletor{
public:
    void operator()(T *ptr){
        delete ptr;
    }
};
  • 自定义删除器,先定义一个类型,该方法不推荐
template<typename T>
class myDeletor{
public:
    void operator()(T *ptr)const{
        cout << "call myDeletor.operator()" << endl;
        delete[]ptr;
    }
};
template<typename T>
class myFileDeletor{
public:
    void operator()(T *ptr)const{
        cout << "call myFileDeletor.operator()" << endl;
        fclose(ptr);
    }
};
int main(){
    unique_ptr<int, myDeletor<int>> p1(new int[100]);
    unique_ptr<FILE, myFileDeletor<FILE>> p2(fopen("data.txt", "w"));
    //先构造的后析构
    return 0;
}
  • 自定义删除器,lambda表达式=》函数对象 function
int main(){
    unique_ptr<int, function<void (int*)>> p1(new int[100],
                [](int *ptr)->void
                {
                    cout << "call lambda release new int[100]" << endl;
                    delete[]ptr;
                });
    unique_ptr<FILE, function<void (FILE*)>> p2(fopen("data.txt", "w"),
                [](FILE *ptr)->void
                {
                    cout << "call lambda release new fopen()" << endl;
                    fclose(ptr);
                });
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值