智能指针上

本文深入探讨了C++中智能指针的概念与应用,包括引入智能指针的原因、不同类型的智能指针及其工作原理。通过具体实例,展示了如何利用智能指针有效管理动态内存,避免内存泄漏等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么引入智能指针?

C++的动态内存需要用户自己维护,动态开辟空间,在出函数作用域或者程序退出时释放内存,否则会造成内存泄漏。当代码教长或者复杂时,我们会忘记释放空间。

void Fun1()
{
    int*p = new int[10];
    FILE* pFile = fopen("1.txt", "r");
    if (pFile == NULL)
    {
        return;    //如果打开失败,返回,造成内存泄漏
    }
    if (p != NULL)
    {
        delete[]p;
        p = NULL;
    }
}

void Fun2()
{
    int *p = new int[10];
    try
    {
        //dosomething();
    }
    catch (...)
    {
        return; //返回之前忘记释放内存,导致内存泄漏
    }
    delete[] p;
}

智能指针的基本情况

1 RAII(资源分配即初始化)

    定义一个类封装资源的分配和释放,在构造函数完成资源的分配和初始化
在析构函数完成资源的清理,可用保证资源正确初始化和释放。

2 像指针一样 operator* operator->
3 赋值和拷贝

Boost库的智能指针
(ps:新的C++11标准中已经引入了unique_ptr/shared_ptr/weak_ptr)
scoped_ptr
scoped_array
shared_ptr
shared_array
weak_ptr
intrusive_ptr
模拟实现库中的部分智能指针
1 auto_ptr
思想:采用的是资源转移的方式,在拷贝和赋值后,原有对象对资源无访问权。

template<typename T>
class AutoPtr
{
public:
    AutoPtr(T* ptr)
        :_ptr(ptr)
    {
        ptr = NULL;//与动态申请的空间脱离
        cout << "AutoPtr()" << endl;
    }
    ~AutoPtr()
    {
        if (NULL != _ptr)
        {
            cout << "~AutoPtr()" << endl;
            delete _ptr;
            _ptr = NULL;
        }
    }
private:
    T* _ptr;
};
void Funtest()
{
    AutoPtr<int> p = new int;
    AutoPtr<int>p1(p); ///p资源被转移到p1,p无法进行访问

}

int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述
不写拷贝构造函数默认浅拷贝,同一块被释放了两次,导致程序崩溃。

改进:添加拷贝构造函数

template<typename T>
class AutoPtr
{
public:
    AutoPtr(T* ptr)
        :_ptr(ptr)
    {
        ptr = NULL;//与动态申请的空间脱离
        cout << "AutoPtr()" << endl;
    }
    AutoPtr(AutoPtr<T>& ptr)  //由于内部要改变ptr的值,所以去掉const
        :_ptr(ptr._ptr)
    {
        ptr = NULL;    //让ptr与动态申请的空间脱离关系
    }
    ~AutoPtr()
    {
        if (NULL != _ptr)
        {
            cout << "~AutoPtr()" << endl;
            delete _ptr;
            _ptr = NULL;
        }
    }
private:
    T* _ptr;
};
void Funtest()
{
    AutoPtr<int> p = new int;
    AutoPtr<int>p1(p); //拷贝构造
}

int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述

智能指针管理权转移:资源被转移,原来的对象无法进行访问,所以只调用了一次析构函数

构造函数的参数要给缺省值

template<typename T>
class AutoPtr
{
public:
    AutoPtr(T* ptr=NULL)
        :_ptr(ptr)
    {
        ptr = NULL;//与动态申请的空间脱离
        cout << "AutoPtr()" << endl;
    }
    AutoPtr(AutoPtr<T>& ptr)  //由于内部要改变ptr的值,所以去掉const
        :_ptr(ptr._ptr)
    {
        ptr = NULL;    //让ptr与动态申请的空间脱离关系
    }
    ~AutoPtr()
    {
        if (NULL != _ptr)
        {
            cout << "~AutoPtr()" << endl;
            delete _ptr;
            _ptr = NULL;
        }
    }
private:
    T* _ptr;
};
void Funtest()
{
    AutoPtr<int> p = new int;
    AutoPtr<int>p1(p); //拷贝构造
    AutoPtr<int> p2;
}

int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述

赋值运算符重载

template<typename T>
class AutoPtr
{
public:
    AutoPtr(T* ptr=NULL)
        :_ptr(ptr)
    {
        cout << "AutoPtr()" << endl;
    }

    AutoPtr(AutoPtr<T>& ptr)  //由于内部要改变ptr的值,所以去掉const
        :_ptr(ptr._ptr)
    {
        ptr._ptr = NULL;    //让ptr与动态申请的空间脱离关系
    }

    AutoPtr<T>& operator=(AutoPtr<T>& ptr)
    {
        if (this != &ptr)
        {
            if (NULL != _ptr) //防止空对象进行引用导致程序崩溃
            {
                delete _ptr;
                _ptr = ptr._ptr;  //如果_ptr为空,则崩溃
                ptr._ptr = NULL;
            }
        }
        return *this;
    }
    ~AutoPtr()
    {
        if (NULL != _ptr)
        {
            cout << "~AutoPtr()" << endl;
            delete _ptr;
            _ptr = NULL;
        }
    }
private:
    T* _ptr;
};
void Funtest()
{
    AutoPtr<int> p = new int;
    AutoPtr<int>p1(p); //拷贝构造
    AutoPtr<int> p2;
    p2 = p1;  //赋值运算符重载 
}

int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述

获取原生指针 重载* 和->

template <typename T>
class AutoPtr
{
public:
    AutoPtr(T* ap = NULL)
        :_p(ap)
    {
        cout << "AutoPtr()" << endl;
    }

    AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
        :_p(ap._p)
    {
        ap._p = NULL;//让ap与动态申请的空间脱离关系
    }

    AutoPtr<T>& operator=(AutoPtr<T>& ap)
    {
        if (this != &ap)
        {
            if (NULL != _p)//防止对空对象进行引用导致程序崩溃
            {
                delete _p;
                _p = ap._p;//如果_p为空,则崩溃
                ap._p = NULL;//脱离关系
            }
        }
        return *this;
    }

    ~AutoPtr()
    {
        if (NULL != _p)
        {
            cout << "~AutoPtr()" << endl;
            delete _p;
            _p = NULL;
        }
    }
    T* Get()
    {
        return _p;
    }
    T* operator*()
    {
        return *_p;
    }
    T* operator->()
    {
        return _p;
    }
private:
    T* _p;
};
void Funtest()
{
    AutoPtr<int> p = new int;
    AutoPtr<int> p1(p);
    AutoPtr<int> p2;
    p2 = p1;
    AutoPtr<int>* p3 = &p1;
    cout << *(p3->Get)() << endl;
}
int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述

auto_ptr

特点:带有缺陷的设计
方式:管理权转移防止一块空间被多次释放(即资源被转移,原对象无法进行访问)

scoped_ptr
思想:让单个对象独占空间,避免对象被多次释放的问题
一个类要防止被拷贝要做两件事情
1》将拷贝构造函数和赋值运算符重载函数的访问权设置为私有
2》将拷贝构造函数和赋值运算符重载函数只声明不定义

template <typename T>
class scopted_ptr
{
public:
    scopted_ptr(T* ptr = NULL)
        :_ptr(ptr)
    {
        cout << "scopted_ptr()" << endl;
    }

    ~scopted_ptr()
    {
        if (NULL != _ptr)
        {
            cout << "~scopted_ptr()" << endl;
            delete _ptr;
            _ptr = NULL;
        }
    }
    T* Get()
    {
        return _ptr;
    }
    T* operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
private:
    scopted_ptr(const scopted_ptr<T>& ptr);
    scopted_ptr<T>& operator=(const scopted_ptr<T>& ptr);
private:
    T* _ptr;
};
void Funtest()
{
    scopted_ptr<int> p1= new int;
    scopted_ptr<int> *p2= &p1;
    cout << *(p2->Get)() << endl;

}
int main()
{
    Funtest();
    system("pause");
    return 0;
}

这里写图片描述

注意:避免出错,不要进行赋值和对象拷贝

引用计数版本的智能指针—->shared_ptr
之前我们学习string类时,运用了写诗拷贝的方法解决浅拷贝存在的问题,同样我们借助它的原理来实现shared_ptr

它在底层多申请了一块空间保存引用计数,用来检测当前对象所管理的空间是否还被其他智能指针使用,在构造函数中,若该智能指针所指空间非空,将其引用计数初始化为1,析构时,先对其引用计数-1,若减为0,就可释放空间,否则不能释放

template<class T>
class SharedPtr
{
public:
    SharedPtr(T* ptr = NULL)
        :_ptr(ptr)
        , _pCount(NULL)
    {
        if (_ptr != NULL)
        {
            _pCount = new int(1);
        }
    }
    SharedPtr(const SharedPtr<T>& sp)
        :_ptr(sp._ptr)
        , _pCount(sp._pCount)
    {
        ++GetRef();
    }

    SharedPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        if (this != &sp)
        {
            Release();
            _ptr = sp._ptr;
            _pCount = sp._pCount;
            ++GetRef();
        }
        return *this;
    }

    T* operator->()
    {
        return _ptr;
    }
    T& operator*()
    {
        return *_ptr;
    }
    int UseCount()
    {
        return GetRef();
    }
    ~SharedPtr()
    {
        Release();
    }
private:
    void Release()
    {
        if (_ptr && 0 == --GetRef())
        {
            delete _ptr;
            delete _pCount;
        }
    }
    int& GetRef()
    {
        return *_pCount;
    }
private:
    T* _ptr;
    int* _pCount;
};

void TestSharedPtr()
{
    SharedPtr<int> sp1(new int(10));
    SharedPtr<int> sp2(sp1);

    *sp1 = 20;
    *sp2 = 30;
    cout << sp1.UseCount() << endl;//2
    cout << sp2.UseCount() << endl;//2

    SharedPtr<int> sp3(new int(50));
    SharedPtr<int> sp4(sp3);

    sp1 = sp3;
    sp2 = sp4;
    cout << sp1.UseCount() << endl;//4
    cout << sp2.UseCount() << endl;//4
}

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

这种方式看似完美,但是也有一个问题
试想有一个双向链表,每个节点的next和prev都是SharedPtr类型的智能指针,则此时的p1,p2引用计数都为2,但函数结束时要销毁p1,p2,p1的引用计数先减1后为1,说明还有其他指针使用这块空间,因此不能释放,P2也是如此,它的引用计数减1后也为1,无法释放,二者相互制约,最终都无法释放导致内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值