C++스마트 포인터 의 매력 다 아 세 요?

17318 단어 C++지능 포인터
전정 개요
정적 메모리 와 스 택 메모 리 를 제외 하고 모든 프로그램 에 메모리 풀 이 하나 더 있다 는 것 을 알 고 있 습 니 다.이 부분 메모 리 는 자유 공간 이나 더미 라 고 합 니 다.프로그램 은 동적 으로 분 배 된 대상,즉 프로그램 이 실 행 될 때 분 배 된 대상 을 더미 로 저장 합 니 다.동적 대상 이 더 이상 사용 하지 않 을 때 우리 의 코드 는 반드시 명시 적 으로 소각 해 야 합 니 다.
C++에서 동적 메모리 관 리 는 한 쌍 의 연산 자 를 사용 하여 이 루어 집 니 다.new 와 delete,ne:동적 메모리 에서 대상 에 게 공간 을 분배 하고 이 대상 을 가리 키 는 지침 을 되 돌려 줍 니 다.delete 는 동적 으로 독점 하 는 지침 을 가리 키 며 대상 을 없 애고 이와 관련 된 내 저장 소 를 방출 합 니 다.
동적 메모리 관 리 는 항상 두 가지 문제 가 발생 한다.하 나 는 메모리 방출 을 잊 어 버 리 면 메모리 가 새 는 것 이다.하 나 는 메모리 인용 지침 이 있 는 상태 에서 방출 되면 불법 메모 리 를 인용 하 는 지침 이 생 긴 다 는 것 이다.
동적 메모리 사용 을 더욱 쉽 고 안전하게 하기 위해 스마트 포인터 라 는 개념 을 도입 했다.지능 지침 의 행 위 는 일반적인 지침 과 유사 하 며,중요 한 차 이 는 가리 키 는 대상 을 자동 으로 방출 하 는 것 이다.
지능 지침 의 원리
RAII:대상 의 생명 주 기 를 이용 하여 프로그램 자원 을 제어 합 니 다.주로 대상 의 구조 함 수 를 통 해 자원 의 관리 권 을 얻 은 다음 에 분석 함 수 를 통 해 관리 하 는 자원 을 방출 한다.그 원 리 는 자원 을 관리 하 는 책임 을 한 대상 에 게 맡 기 는 것 이다.

//RAII
template<class T>
class SmartPtr
{
public:
	//           
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	//          
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

class A
{
private:
	int _a = 10;
};

void test()
{
	//    
	int* ptr = new int[10];
	SmartPtr<int> sp(ptr);
	//     --            
	SmartPtr<int> sp2(new int);
	SmartPtr<A> sp3(new A);
}
이것 은 결코 스마트 포인터 대상 이 아니 므 로 스마트 지침 은 다음 과 같은 조건 을 만족 시 켜 야 한다.
RAII 사상 실현4.567917.사용 방식 은 포인터 와 같 습 니 다.예 를 들 어*인용 과->조작 을 지원 해 야 합 니 다클래스 에 다음 작업 의 과부하 를 추가 해 야 합 니 다.

T* operator->()
	{
		return _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
스마트 포인터 와 일반적인 포인터 의 차이 중 하 나 는 스마트 포인터 가 수 동 으로 공간 을 방출 할 필요 가 없다 는 것 이다.

void test()
{
	//    --             --       
	SmartPtr<A> sp(new A);
	(*sp)._a = 10;
	sp->_a = 100;

	//    --      
	int* p = new int;
	A* pa = new A;
	*p = 1;
	pa->_a = 10;
	//return  //                
	delete p;
	delete pa;
}
C++표준 라 이브 러 리 의 스마트 포인터 사용
라 이브 러 리 의 지능 지침 은 auto 로 나 뉜 다.ptr、unique_ptr、 share_ptr
그들 은 모두 헤더 파일 을 도입 해 야 사용 할 수 있다.
auto_ptr
auto_ptr 는 결함 이 있 는 스마트 포인터 입 니 다(비활성화)

#include <memory>
using namespace std;
void test()
{
	auto_ptr<int> ap(new int);
	auto_ptr<int> ap2(new int(2));
	*ap = 10;
	*ap2 = 20;
}
auto_ptr 포인터 가 할당 작업 을 할 때 자원 을 이전 합 니 다.여러 개의 스마트 포인터 가 같은 메모리 자원 을 가리 키 는 것 을 방지 하기 위해 서 입 니 다.그러나 이런 디자인 은 분명히 우리 의 요구 에 부합 되 지 않 는 다.
在这里插入图片描述
저희 가 간단하게 시 뮬 레이 션 을 해서 auto 를 실현 하도록 하 겠 습 니 다.ptr,그의 밑바닥 이 어떻게 자원 권 의 이전 을 진행 하 는 지 봅 시다.

//  auto_ptr
template<class T>
class Auto_ptr
{
public:
	Auto_ptr(T* ptr)
		:_ptr(ptr)
	{}
	~Auto_ptr()
	{
		if (_ptr)
			delete _ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}

	Auto_ptr(Auto_ptr<T>& ap)
		:_ptr(ap._ptr)
	{
		//        
		ap._ptr = nullptr;
	}

	Auto_ptr<T>& operator=(Auto_ptr<T>& ap)
	{
		if (this != &ap)
		{
			if (_ptr)
				delete _ptr;
			//        
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		return *this;
	}
private:
	T* _ptr;
};
unique_ptr
unique_ptr 스마트 포인 터 는 복사 방지 로 자원 관리 권한 이전 문 제 를 해결 하 는 것 입 니 다-유 니 크ptr 할당 연산 자 함수 와 복사 구조 함수 삭제 함수 로 설정

void test()
{
	unique_ptr<int> up(new int(10));
	unique_ptr<int> up2(up);//error
	unique_ptr<int> up3(new int(20));
	up = up3; //error
}
오류 원인:복사 구조 와 할당 재 업로드 함 수 는 모두 삭 제 된 함수 입 니 다.
在这里插入图片描述
기본 구현:

template<class T>
class Unique_ptr
{
public:
	Unique_ptr(T* ptr)
		:_ptr(ptr)
	{}

	Unique_ptr(const Unique_ptr<T>& up) = delete;
	Unique_ptr<T>& operator=(const Unique_ptr<T>& up) = delete;

	~Unique_ptr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}
private:
	T* _ptr;
};
shared_ptr
shared_ptr 는 C++11 에서 새로 제공 한 스마트 포인터 로 자원 관리 권한 이전 문 제 를 해결 할 뿐만 아니 라 신뢰성 있 는 복사 기능 도 제공 합 니 다.

class A
{
public:
	int _a = 10;
	~A()
	{
		cout << "~A()" << endl;
	}
};

void test()
{
	shared_ptr<A> sp(new A);
	shared_ptr<A> sp2(new A);
	shared_ptr<A> sp3(sp2);//ok
	sp3 = sp;//ok
	sp->_a = 100;
	sp2->_a = 1000;
	sp3->_a = 10000;
	cout << sp->_a << endl;
	cout << sp2->_a << endl;
	cout << sp3->_a << endl;
}
실행 결과:
在这里插入图片描述
우 리 는 신청 한 자원 이 얼마나 많은 자원 을 방출 하 는 지 발견 했다.이때 sp 와 sp3 는 하나의 자원 을 공유 하고 sp3 를 수정 하 는 것 도 sp 를 수정 하 는 것 과 같다.그래서 결국 10000 원 을 인쇄 합 니 다.그러면 하나의 자원 을 공 유 했 는데 어떻게 자원 을 한 번 만 방출 하 는 것 을 실현 합 니까?인용 계수
우 리 는 shared 를 통 해ptr 가 제공 하 는 인터페이스#include <memory>현재 몇 개의 스마트 포인터 가 같은 자원 을 관리 하고 있 는 지 확인 합 니 다.

void test()
{
	shared_ptr<A> sp(new A);
	cout << sp.use_count() << endl;//1
	shared_ptr<A> sp2(sp);
	cout << sp.use_count() << endl;//2
	cout << sp2.use_count() << endl;//2
	shared_ptr<A> sp3(new A);
	cout << sp.use_count() << endl;//2
	cout << sp2.use_count() << endl;//2
	cout << sp3.use_count() << endl;//1
	sp3 = sp;
	sp3 = sp2;
	cout << sp.use_count() << endl;//2
	cout << sp2.use_count() << endl;//2
	cout << sp3.use_count() << endl;//2
}
캡 처 실행:중간 에 분석 함수 가 있 는 이 유 는 sp3 가 sp 를 가리 킬 때 sp3 의 인용 계수 가 0 이면 분석 함수 로 자원 을 방출 하기 때 문 입 니 다.이때 sp 가 만 든 자원 은 세 개의 스마트 포인터 로 관리 된다.
在这里插入图片描述
도해:
在这里插入图片描述
이 를 실현 할 때 우 리 는 하나의 자원 이 하나의 카운터 에 만 대응 하 는 것 을 확보 해 야 한다.모든 스마트 포인터 가 각자 의 카운터 가 있 는 것 이 아니 라.그래서 우 리 는 자원 과 계산 기 를 연결 할 수 있 습 니 다.이때 같은 자원 을 가리 키 는 스마트 포인터 이 고 방문 하 는 것 도 모두 같은 카운터 입 니 다.
구성원 변수:구성원 변 수 는 두 개의 변수 가 있어 야 합 니 다.각각 자원 포인터 의 변수ptr 와 계수기 변수countPtr,그들 은 모두 포인터 형식의 변수 입 니 다.
복사 구조 함수:복사 구조 함수 에서 현재 대상 의 지침 을 복사 할 대상 의 자원 을 가리 키 고 계수 기 를 복사 해 야 합 니 다.마지막 으로 계수 기 를++해 야 합 니 다.
할당 연산 자 재 업로드:우 리 는 두 대상 이 같은 지 판단 할 수 없 으 며,두 대상 의 자원 이 다 르 면 할당 이 필요 합 니 다.할당 에서 현재 대상 의 계수 기 를 C 로 만 들 고 0 이면 현재 대상 의 자원 이 현재 대상 에 의 해 만 관리 되 는 것 임 을 나타 내 며 자원 을 방출 해 야 합 니 다.그리고 현재 대상 포인 터 를 복사 할 대상 의 자원 으로 변경 하고 계수 기 를 복사 합 니 다.마지막 으로 카운터 에+작업 을 해 야 합 니 다.
분석 함수:현재 대상 의 자원 을 판단 하 는 카운터 입 니 다.먼저 C 작업 을 하고 카운터 가 0 인지 아 닌 지 를 판단 합 니 다.0 이 어야 자원 을 방출 하고 0 이 아니면 아무것도 하지 않 습 니 다.

template<class T>
class Shared_ptr
{
public:
	Shared_ptr(T* ptr)
		:_ptr(ptr)
		, _countPtr(new size_t(1))//    1
	{}
	Shared_ptr(const Shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _countPtr(sp._countPtr)
	{
		//     
		++(*_countPtr);
	}
	Shared_ptr<T> operator=(const Shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			//       
			//    0,           
			if (--(*_countPtr) == 0)
			{
				delete _ptr;
				delete _countPtr;
			}

			_ptr = sp._ptr;
			_countPtr = sp._countPtr;

			++(*_countPtr);
		}
		return *this;
	}

	~Shared_ptr()
	{
		//     
		if (--(*_countPtr) == 0)
		{
			delete _ptr;
			delete _countPtr;
			_ptr = nullptr;
			_countPtr = nullptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	size_t* _countPtr;//     
};
우리 가 이 룬 sharedptr 스마트 포인터 가 다 중 스 레 드 장면 에서 스 레 드 안전 문제 가 존재 하지 않 습 니 다.-인용 카운터 포인터 는 공유 변수 이 고 여러 스 레 드 를 수정 할 때 카운터 가 혼 란 스 러 울 수 있 습 니 다.자원 이 앞 당 겨 방출 되 거나 메모리 누 출 문제 가 발생 할 수 있 습 니 다.
코드 를 살 펴 보 겠 습 니 다.안전 하 다 면 마지막 분석 함 수 는 한 번 만 호출 되 어야 합 니 다.

void fun(const Shared_ptr<A>& sp, int n)
{
	for (int i = 0; i < n; ++i)
		Shared_ptr<A> copy(sp);//  copy    
}

void test()
{
	Shared_ptr<A> sp(new A);
	int n = 100000;
	thread t1(fun, ref(sp), n);
	thread t2(fun, ref(sp), n);
	t1.join();
	t2.join();
}
실행 결과 1:대상 의 분석 함 수 를 호출 하지 않 은 것 을 발 견 했 습 니 다.이 때 메모리 누 출 문제 가 발생 했 음 을 설명 합 니 다.
在这里插入图片描述
실행 결과 2:석조 함 수 를 두 번 호출 하면 자원 이 두 번 방출 된다 는 것 을 설명 합 니 다.
在这里插入图片描述
우 리 는 클래스 에서 계수기 의 값 을 가 져 오 는 인 터 페 이 스 를 제공 할 수 있다.

size_t getCount()
	{
		return *_countPtr;
	}
그리고 코드 에서 실행 하고 계수기 의 값 을 가 져 옵 니 다.계수기 의 값 이 0 이 아니 기 때문에 호출 석조 함 수 를 호출 하지 않 습 니 다.
在这里插入图片描述
그래서 우 리 는 계산 기 를 수정 하 는 곳 에서 잠 금 보 호 를 할 수 있다.이 자 물 쇠 는 전역 변수의 자물쇠 가 될 수 없고 자원 간 에 영향 을 받 을 수 없습니다.그렇지 않 으 면 한 자원 이 자 물 쇠 를 추가 하여 수정 할 때 다른 자원 이 영향 을 받 을 수 있 습 니 다.이 때 코드 의 집행 효율 에 영향 을 줄 수 있 습 니 다.모든 카운터 에 별도의 자 물 쇠 를 제공 해 야 한다.
여기+작업 과 C 작업 을 모두 봉인 합 니 다.

template<class T>
class Shared_ptr
{
public:
	Shared_ptr(T* ptr)
		:_ptr(ptr)
		, _countPtr(new size_t(1))//    1
		, _mtx(new mutex)
	{}
	Shared_ptr(const Shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _countPtr(sp._countPtr)
		,_mtx(sp._mtx)
	{
		//     
		//++(*_countPtr);
		addCount();
	}
	Shared_ptr<T> operator=(const Shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			//       
			//    0,           
			//if (--(*_countPtr) == 0)
			if (subCount() == 0)
			{
				delete _ptr;
				delete _countPtr;
				delete _mtx;
			}

			_ptr = sp._ptr;
			_countPtr = sp._countPtr;

			addCount();
		}
		return *this;
	}

	~Shared_ptr()
	{
		//     
		if (subCount() == 0)
		{
			delete _ptr;
			delete _countPtr;
			delete _mtx;
			_ptr = nullptr;
			_countPtr = nullptr;
			_mtx = nullptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

	size_t getCount()
	{
		return *_countPtr;
	}

	size_t addCount()
	{
		_mtx->lock();
		++(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}

	size_t subCount()
	{
		_mtx->lock();
		--(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}
private:
	T* _ptr;
	size_t* _countPtr;//     
	mutex* _mtx;
};
운행 결과:우 리 는 다 중 스 레 드 장면 에서 도 정상적으로 방출 할 수 있다 는 것 을 발견 했다.
在这里插入图片描述
반복 참조 문제
shared_ptr 에 도 작은 문제 가 존재 합 니 다.즉,순환 참조 문제 입 니 다.
다음 코드 부터 살 펴 보 겠 습 니 다.

struct ListNode
{
	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;
	int _data;

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

void test()
{
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}
실행 결과:우 리 는 자원 을 방출 하지 않 았 고 카운터 도 증가 하고 있 음 을 발견 했다.
在这里插入图片描述
도해:
在这里插入图片描述
在这里插入图片描述
C++11 에서 이 문 제 를 해결 하기 위해 새로운 스마트 포인터 waek 를 도입 하 였 습 니 다.ptr,이 지침 을 약 한 지침 이 라 고 합 니 다.값 을 부여 하거나 복사 할 때 계수 기 는++를 하지 않 습 니 다.구 조 를 분석 할 때 도 진정한 자원 의 방출 을 하지 않 는 다.waek_ptr 는 단독으로 사용 할 수 없습니다.그 가장 큰 역할 은 shared 를 해결 하 는 것 입 니 다.ptr 순환 참조 문제.

struct ListNode
{
	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;
	int _data;

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

void test()
{
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}
실행 결과:
在这里插入图片描述
우리 스스로 이 루 는 sharedptr 에서 저 희 는 자원 을 방출 할 때 delete 로 만 방출 합 니 다.저희 가 공간 을 신청 하 는 방식 에서 new 로 만 공간 을 신청 하 는 것 이 아니 라 malloc 로 신청 할 수도 있 습 니 다.이때 free 로 풀 어야 합 니 다.그래서 우 리 는 스마트 포인터 에 삭제 기 를 추가 해 야 한다.

void test()
{
	Shared_ptr<A> sp(new A[100]);//       
}
삭제 기 는 주로 모방 함 수 를 통 해 이 루어 질 수 있다.

template<class T>
struct DeleteDel
{
	void operator()(T* ptr)
	{
		delete ptr;
	}
};

template<class T>
struct FreeDel
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};

template<class T>
struct DeleteArrDel
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

template<class T, class Del = DeleteDel<T>>
class Shared_ptr
{
public:
	Shared_ptr(T* ptr)
		:_ptr(ptr)
		, _countPtr(new size_t(1))//    1
		, _mtx(new mutex)
	{}
	Shared_ptr(const Shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _countPtr(sp._countPtr)
		,_mtx(sp._mtx)
	{
		//     
		//++(*_countPtr);
		addCount();
	}
	Shared_ptr<T> operator=(const Shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			//       
			//    0,           
			//if (--(*_countPtr) == 0)
			if (subCount() == 0)
			{
				//delete _ptr;
				//          
				_del(_ptr);
				delete _countPtr;
				delete _mtx;
			}

			_ptr = sp._ptr;
			_countPtr = sp._countPtr;

			addCount();
		}
		return *this;
	}

	~Shared_ptr()
	{
		//     
		if (subCount() == 0)
		{
			//delete _ptr;
			//          
			_del(_ptr);
			delete _countPtr;
			delete _mtx;
			_ptr = nullptr;
			_countPtr = nullptr;
			_mtx = nullptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

	size_t getCount()
	{
		return *_countPtr;
	}

	size_t addCount()
	{
		_mtx->lock();
		++(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}

	size_t subCount()
	{
		_mtx->lock();
		--(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}
private:
	T* _ptr;
	size_t* _countPtr;//     
	mutex* _mtx;
	Del _del;
};
在这里插入图片描述
이상 은 C++한 편의 글 에서 스마트 포인터 의 매력 에 대한 상세 한 내용 을 알 수 있 습 니 다.C+스마트 포인터 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 하 세 요!

좋은 웹페이지 즐겨찾기