问题引出
因为C++ 中没有垃圾回收机制, 在用new完对象后很容易忘记delete这个对象,从而造成内存泄漏。而利用在函数栈退出时栈上的对象会被释放这个特征, 由此可以联想到用一个其他的对象将new出的资源进行管理, 只要函数栈跳出, 那么这个对象的析构函数顺带将其管理的内存释放掉, 从而组织内存泄漏
RAll 思想
template<class T> class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr) {}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
private: T* _ptr;
};
但是RAII 不能称为智能指针因为还不具备指针的操作
在利用RAII的思想 然后再重载 * -> 使之具有指针的操作那么就是所谓的可以管理资源的智能指针了;
但是 : 有个新的问题 智能指针是一个类的封装, 实例对象的时候会有拷贝构造函数, 那么就有可能为多个对象同时管理一个资源的情况, 这样再智能指针对象析构的时候, 每个对象都会对目标区域析构一次,从而造成多次释放导致程序崩溃.
注意: 注意if判空是对自己指针值判空,目标区域释放没有释放不清楚;
auto_ptr
auto_ptr : 1) 通过移交管理权的思想, 将资源转移到一个新的auto_ptr 的对象上, 同时将原对象的指针置为nullptr;
2) 在发生赋值的时候将原管理的对象进行释放, 同时获得新的资源
实现原理如下:
#pragma once
template <class T>
class Auto_ptr
{
private:
T* _ptr;
public:
Auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
~Auto_ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
Auto_ptr(Auto_ptr& ptr)
:_ptr(ptr._ptr)
{
ptr._ptr = nullptr;
}
Auto_ptr & operator = (Auto_ptr& ptr) // 释放旧资源获得新资源
{
if (this != &ptr)
{
if (_ptr)
{
delete _ptr;
}
_ptr = ptr._ptr;
ptr._ptr = nullptr;
}
return *this;
}
T & operator * ()
{
return *_ptr;
}
T * operator -> ()
{
return _ptr;
}
};
测试代码:
#include <iostream>
#include "auto_ptr.h"
#include <string>
using namespace std;
string* test()
{
string* a = new string("hello world");
cout << *a<< endl;
Auto_ptr<string> p(a);
cout << *p << endl;
Auto_ptr<string> p2(p);
// cout << *p << endl; // 此句代码崩溃 因为p中的指针以及被置为空
p = p2;
// cout << *p2 << endl; // 此句代码崩溃因为p2的资源已经交给了p;
cout << *p << endl;
return a;
}
void test1()
{
int a = 1;
int b = 2;
a = a + b;
}
int main()
{
string* a =test();
test1();
std::cout << *a; // 在使用智能指针后 *a 无法打印 因为在栈退出的时候会释放Auto_ptr 的对象
// 在析构的时候顺带把 堆上的内存也析构调了.
}
注意: 现在再从实现原理层来分析会发现,这里拷贝后把p2对象的指针赋空了,导致p2对象悬空 // 通过p2对象访问资源时就会出现问题。
在main函数中“hello world” 没有打印出来, 因为资源已经被auto_ptr 的对象释放
unique_ptr
通过将拷贝构造函数和 = 的重载全部设置为私有c++98, 或者直接将其删除c++11
简单实现如下:
template <class T>
class Unique_ptr
{
private:
T* _str;
public:
Unique_ptr(T* str = nullptr)
:_str(str)
{}
T& operator * ()
{
return *_str;
}
T* operator -> ()
{
return _str;
}
Unique_ptr & operator = (Unique_ptr<T> & ptr) = delete;
Unique_ptr(Unique_ptr<T>& ptr) = delete;
};
使用C++11自带的unique_ptr 可以看到这个函数已经被删除
方式十分暴力, 直接删除了,但是有时候有需要进行拷贝, 或者赋值 有没有更加优雅的办法呢?
shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
template <class T>
class Shared_ptr {
private:
T* _ptr;
int* _count;
mutex _pMutex;
void add_count()
{
_pMutex->lock();
*_count++;
_pMutex->unlock();
}
void Release()
{
bool detection = false;
_pMutex->lock();
_count--;
if (_count == 0)
{
delete _ptr;
delete _count;
detection = true;
}
_pMutex->unlock();
if (detection == true)
delete _pMutex;
}
public:
Shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _count(new int(1))
, _pMutex(new mutex)
{}
~Shared_ptr()
{
Release();
}
Shared_ptr(Shared_ptr<T>& ptr)
{
_ptr = ptr._ptr;
_count = ptr._count;
_pMutex = ptr._pMutex;
add_count();
}
Shared_ptr & operator = (Shared_ptr<T>& ptr)
{
if (this != &ptr)
{
Release();
_ptr = ptr;
_count = ptr._count;
_pMutex = ptr._pMutex;
add_count();
}
return *this;
}
T& operator * ()
{
return *_ptr;
}
T* operator -> ()
{
return _ptr;
}
};
亮点:通过在堆上申请内存, 然后在拷贝构造函数和 = 的重载中将同一份资源的数量联系在一起, 只有当内部的计数为0的时候才进行释放, 有了拷贝和赋值整个指针用起来更加的灵活