스마트 포인터가 필요한 이유
코드 작성시 힙에서 적용된 공간이 일부 이유 (코드 중간에 비정상)로 인해 해제되지 않아 코드에서 메모리 누수가 발생했기 때문에 이러한 문제를 방지하기 위해 스마트 포인터가 등장했습니다. . 스마트 포인터를 사용하면 포인터가 사용되지 않는 경우 자동으로 감지 할 수 있으며 힙의 공간이 자동으로 해제됩니다.
스마트 포인터의 역할 포인터를
관리하고 리소스를 자동으로 해제합니다.
RAII
RAII는 개체 수명주기를 사용하여 프로그램 리소스를 제어하는 기술입니다.
클래스에서는 객체가 생성 될 때 자원을 획득하고 마지막으로 객체가 파괴 된 후 자원이 해제되므로 메모리 자원이 해제되지 않을까 걱정할 필요가 없습니다. 적용된 리소스에 대한 포인터가 관리를 위해 클래스 (관리 : 리소스 해제)로 넘겨지면 함수의 끝에서 컴파일러가 클래스의 소멸자를 자동으로 호출하여 클래스 관리 리소스의 해제를 완료합니다. 실제로 포인터는 관리를 위해 클래스로 넘겨지고 리소스는 생성자에서 적용되고 리소스는 소멸자에서 해제됩니다.
- 장점
리소스 릴리스를 표시 할 필요가 없으며
개체는 항상 사용 중에 유효합니다.
동시에 RAII는 클래스를 포인터와 같은 방식으로 만들어야하며 두 연산자 * 및-> 만 오버로드하면됩니다.
단점 : RAII에는 얕은 복사 문제가 있습니다.
template<class T>
class Smartptr
{
public:
Smartptr(T *ptr = nullptr)
:_ptr(ptr)
{
}
~Smartptr()
{
if(_ptr)
delete _ptr;
}
private:
T* _ptr;
};
void testSmartptr()
{
int *p = new int;
Smartptr<int> sp(p);
}
스마트 포인터의 원리
위에서 언급 한 RAII의 구현 방법은 포인터의 몇 가지 고유 한 동작이 없기 때문에 스마트 포인터로 간주 할 수 없습니다.
- 역 참조 *
- 작동 지점->
따라서 RAII 클래스에서 위의 두 작업 만 오버로드하면됩니다.
template<class T>
class Smartptr
{
public:
Smartptr(T *ptr = nullptr)
:_ptr(ptr)
{
}
~Smartptr()
{
if (_ptr)
delete _ptr;
}
T& operator*() //指针所指向空间里面的内容
{
return *_ptr;
}
T* operator->()//返回的是指针的地址
{
return _ptr;
}
private:
T* _ptr;
};
struct Date
{
int a;
int b;
};
void testSmartptr()
{
Smartptr<int> sp(new int); //sp相当于一个对象,用来管理资源
*sp = 10;
cout << *sp << endl;
Smartptr<Date> sp2(new Date);
sp2->a = 1;
sp2->b = 2;
cout << sp2->a << sp2->b << endl;
}
위의 방법은 복사 생성자 및 할당 연산자 오버로딩 방법에서 실행 가능하지 않으며 얕은 복사본을 생성하기 쉽습니다. 그러나 딥 카피는 2 개의 스페이스를 생성하기 때문에 문제를 해결하는 데 사용할 수 없습니다. 동시에 외부 사용자가 리소스를 제공합니다. 스마트 포인터는 스페이스 신청 권한이 없으며 리소스 만 관리 할 수 있습니다.
- C ++ 98에서 autoPtr의 원리
autoPtr 라이브러리 함수에서 실현 원칙은 복사 생성자 및 할당 연산자 오버로딩에서 후자 개체의 내용을 이전 개체로 전송하고 마지막으로 후자 개체의 내용을 비우는 것입니다.
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{
}
~AutoPtr()
{
if (_ptr)
delete _ptr;
}
AutoPtr(AutoPtr<T>&sp) //拷贝构造函数
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
AutoPtr<T>& operator=(AutoPtr<T>&sp)
{
if (*this != sp) //先判断是否自己给自己赋值
{
if (_ptr) //如果赋值等号前面的有空间,需将其释放
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
AutoPtr* operator->()
{
return this;
}
AutoPtr& operator*()
{
return *this;
}
private:
T* _ptr;
};
딥 복사 및 할당에는 하나의 복사본 만 사용할 수 있으므로 C ++ 표준위원회에서는 권장하지 않습니다.
auto_ptr의 개선 된 버전 :
구현 원칙 : 리소스 해제 권한을 관리하기 위해 bool owner 매개 변수를 추가합니다.
if (_ptr && _owner) //资源存在,同时拥有资源释放的权利
delete _ptr;
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _owner(false)
{
if (_ptr)
_owner = true;
}
auto_ptr( auto_ptr<T>& p)
:_ptr(p._ptr)
,_owner(p._owner)
{
p._owner = false;
}
auto_ptr& operator*()
{
return *_ptr;
}
auto_ptr* operator->()
{
return _ptr;
}
auto_ptr<T>& operator = (auto_ptr& p)
{
if (this != &p)
{
if (_ptr && _owner)
delete _ptr;
_ptr = p._ptr;
_owner = p._owner;
p._owner = false;
}
return *this;
}
~auto_ptr()
{
if (_ptr && _owner)
delete _ptr;
}
private:
T* _ptr;
bool _owner;
};
auto_ptr의 수정 된 버전으로 인해 와일드 포인터가 발생할 수 있습니다.
- unique_ptr 라이브러리 함수는 스마트 포인터가 얕은 복사에 취약하기 때문에 복사 생성자 및 할당 연산자의 오버로딩이 금지되고 복사 생성자 및 할당 연산자 오버로딩이 전용 멤버 함수로 작성됩니다.
template<class T>
class Uniqueptr
{
public:
Uniqueptr(T* ptr = nullptr)
:_ptr(ptr)
{
}
~Uniqueptr()
{
if (_ptr)
delete _ptr;[]
}
Uniqueptr* operator->()
{
return this;
}
Uniqueptr& operator*()
{
return *this;
}
Uniqueptr(Uniqueptr<T>const &) = delete;
Uniqueptr<T>& operator = (Uniqueptr<T>const &) = delete;
private:
T* _ptr;
};
- shared_ptr
은 세어 자원의 공유를 실현하는 것입니다. 개수를 계산하여 자원 해제가 필요한지 판단하면 shared_ptr은 각 자원에 대한 개수를 유지하여 각 자원이 여러 객체에서 공유되고 있음을 기록하는 데 사용됩니다. 객체가 소멸자를 호출하면 개수는 -1이고 개수가 0에 도달하면 마지막 자원을 적용 할 수없고 자원이 최종적으로 해제됨을 의미합니다. 개수가 0이 아니면 리소스가 아직 사용 중이며 리소스를 해제 할 수 없음을 의미합니다.
shared_ptr = RAII + 연산자 * + opeartor-> + 개수
#include<mutex> //并发程序互斥锁
template<class T>
class shared_ptr
{
public:
shared_ptr(T *ptr)
:_ptr(ptr)
,_pcount(new int(1))
//,_pmutex(new mutex)
{
}
~shared_ptr()
{
Release();
}
shared_ptr(const shared_ptr<T> &sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
//,_pmutex(sp._pmutex)
{
addcount();
}
shared_ptr<T>& operator=(const shared_ptr<T> &sp)
{
if (_ptr != sp._ptr)
{
Release(); //释放旧空间
_ptr = sp._ptr;
_pcount = sp._pcount;
addcount(); //计数+1
//_pmutex = sp._pmutex;
}
return *this;
}
T& operator*()
{
return *this;
}
T* operator->()
{
return this;
}
void addcount()
{
//_pmutex->lock(); //加锁
++(*_pcount); //计数+1
//_pmutex->unlock();//解锁
}
void Release()
{
//bool deleteflag = false;
if (0 == --(*_pcount))
{
delete _ptr;
delete _pcount;
}
/*if (deleteflag == ture)
{
delete _pmutex;
}*/
}
int usecount()
{
return *_pcount;
}
private:
T * _ptr;
int *_pcount;
//mutex* _pmutex; 多线程加锁
};
Shared_ptr이 개선되었습니다 .shared_ptr로 인해 관리 포인터에는 여러 응용 프로그램 메서드가있어 릴리스 함수를 고정 된 것으로 작성할 수 없으므로 소멸자는 템플릿 전문화가 필요합니다.
//定制删除器
template<class T>
class DFDel //默认new出来的空间
{
public:
void operator()(T*& p)
{
if (p)
{
delete p;
p = nullptr;
}
}
};
template<class T>
class Free //malloc申请的空间
{
public:
void operator()(T*& p)
{
if (p)
{
free(p);
p = nullptr;
}
}
};
class Fclose
{
public:
void operator()(FILE* p)
{
if (p)
{
fclose(p);
p = nullptr;
}
}
};
namespace bai
{
template<class T,class DF = DFDel<T>> //DF为删除类型,DF默认为new出来的空间
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
{
if (_ptr)
_pcount = new int(1);
}
shared_ptr(const shared_ptr<T>& p)
:_ptr(p._ptr)
, _pcount(p._pcount)
{
if (_ptr) // 如果资源不为nullptr
++(*_pcount);
}
//p1 == p2
//p1:未管理资源------直接p2共享
//p1:单独管理资源----在于p2共享之前先释放自己的资源
//p1:与其他对象共享资源---p1计数--,p2计数++
shared_ptr<T> operator = (const shared_ptr<T>& p)
{
if (this != &p)
{
if (_ptr && 0 == --(*_pcount))
{
delete _ptr;
delete _pcount;
}
_ptr = p._ptr;
_pcount = p._pcount;
if (_ptr)
++(*_pcount);
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~shared_ptr()
{
if (_ptr && --(*_pcount) == 0) //由于资源的申请方式不同,
{
//所以需要根据资源的类型,定制释放的方式
//delete _ptr;
DF()(_ptr); //DF相当于一种类型,DF()相当于创建一个无名的对象,DF()(_ptr),相当于DF对象调用
delete _pcount;
}
}
int usecount()
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
};
}
void TestFunc()
{
bai::shared_ptr<int,DFDel<int>> p1(new int);
bai::shared_ptr<int,DFDel<int>> p2(p1);
bai::shared_ptr<FILE, Fclose> p3(fopen("666.text" ,"rb"));
}
shared_ptr의 결함 :
스마트 포인터를 사용하여 이중 연결 목록을 관리 할 때 순환 참조가 발생하기 쉽습니다.
struct ListNode
{
ListNode(int data = int())
:_pre(nullptr)
, _next(nullptr)
{
cout << "ListNode()" << this << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
shared_ptr<ListNode> _pre;
shared_ptr<ListNode> _next;
int data;
};
void TestFunc()
{
shared_ptr<ListNode> p1(new ListNode(10));
shared_ptr<ListNode> p2(new ListNode(20));
cout << p1.use_count() << endl;//查看p1中的引用计数
cout << p2.use_count() << endl;
p1->_next = p2;
p2->_pre = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
}
소멸자가 호출되지 않아
shared_ptr 순환 참조
weak_ptr 를 해결하기 위해 리소스 누수가 발생합니다 . 리소스는 별도로 관리 할 수 없으며 shared_ptr과 함께 사용해야합니다.
weak_ptr : shared_ptr이 존재하는 순환 참조를 해결하는 것입니다.
struct ListNode
{
ListNode(int _data = int())
:data(_data)
{
cout << "ListNode()" << this << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
weak_ptr<ListNode> _pre; //****
weak_ptr<ListNode> _next;
int data;
};
weak_ptr은 리소스를 단독으로 관리 할 수 없기 때문에 생성자는 _pre, _next를 초기화 할 수 없습니다.