언어/C++

스마트 포인터

tsyang 2020. 11. 8. 12:24

RAII


RAII : Resource Acquisition Is Initialization 

 

리소스의 라이프사이클과 오브젝트의 라이프사이클을 일치시킨다는 뜻이다.

 

여기서 리소스란 메모리, 쓰레드, 파일IO, 뮤텍스 ,DB와의 커넥션 등이 있다.

 

C++ 에서는 스마트 포인터를 이용하여 메모리 리소스와 인스턴스의 라이프사이클을 일치시킬 수 있다.

 

 

unique_ptr


유니크 포인터는 exlusive ownership을 제공하는 스마트 포인터다.

 

유니크 포인터는 하나의 오브젝트(메모리 영역)을 단 하나의 포인터만 가리키게 한다.

 

include<memory>

...


class Cat
{
public :
	Cat() : mAge{1} {}
    ~Cat(){}
    void speak(){cout << "Meow" <<endl;}
private
	int mAge;
};

int main()
{
  std::unique_ptr<Cat> catPtr = std::make_unique<Cat>();
  std::unique_ptr<Cat> catPtr1 = catPtr ; // 컴파일 에러
  std::unique_ptr<Cat> catPtr2 = std::move(catPtr);  // 소유권 넘김
}

같은 메모리 영역을 참조하려고 하면 컴파일 에러가 난다. move로 소유권을 넘겨주는건 괜찮다.

 

변수가 선언된 스코프를 넘어가면 스택 영역의 인스턴스가 소멸하며 메모리도 해제된다.


클래스의 멤버 변수에 유니크 포인터가 있는 경우, 할당시에 오류가 발생할 수 있다. (exclusive ownership 위반) 따라서 멤버 변수에 유니크 포인터가 있는 경우 적절하게 복사 생성/할당자를 구현해 줘야 한다.

shared_ptr 


쉐어드 포인터는 유니크 포인터와 다르게 같은 메모리 영역을 동시에 참조가 가능하다. 

 

참조하는 메모리영역에 counter를 둬서 count가 0이 되면 메모리를 해제한다.

 

int main()
{
  std::shared_ptr<Cat> catPtr = std::make_shared<Cat>(); 
  cout << catPtr.use_count(); //카운트 1
  std::shared_ptr<Cat> catPtr1 = catPtr; 
  cout << catPtr.use_count(); //카운트 2
}

 

주의할 점은 circular reference가 발생할 수도 있는데,

 

Cat()
{
//...
std::shared_ptr<Cat> friend;
}


std::shared_ptr<Cat> mPtr = std::make_shared<Cat>();
std::shared_ptr<Cat> mPtr2 = std::make_shared<Cat>();

mPtr2->friend = mPtr;
mPtr1->friend = mPtr2; 

이런식으로 circular reference가 생겨버리면 카운트가 0이 되지 않기 때문에 memory leak이 발생한다.

 

클래스의 멤버 변수에 쉐어드 포인트가 있는 경우는 유니크 포인터처럼 처리하기가 애매한데, 사용하는 사람이 얕은 복사를 원하는지 깊은 복사를 원하는지 알 수 없기 때문이다. 그렇기 때문에 clone메서드를 구현하고 주석으로 명시해 주는 것이 좋다.

weak_ptr


weak_ptr은 shared_ptr의 reference counter를 증가시키지 않는다.

 

그렇기 때문에 위와같은 circular reference를 방지할 수 있다.

 

Cat()
{
//...
std::weak_ptr<Cat> friend; // reference count를 증가시키지 않아서 메모리 릭이 일어나지 않음
}

std::shared_ptr<Cat> mPtr = std::make_shared<Cat>();
std::shared_ptr<Cat> mPtr2 = std::make_shared<Cat>();

mPtr2->friend = mPtr;
mPtr1->friend = mPtr2; 

 

특이한 점은 항상 lock()이라는 메서드를 통해 shared ptr을 만든 뒤 사용해야 한다는 것이다.

std::weak_ptr<Cat> wPtr;
std::shared_ptr<Cat> sPtr = std::make_shared<Cat>();
wPtr= sPtr;

if(const auto spt = wPtr.lock())
{
  std::cout<<"count:" << sPtr.use_count() <<endl; // 카운트 1 증가해있음
  spt->speak();
}
else
{
	cout<<"아무것도 가리키지 않음"<<endl;
}

 lock()은 하나의 shared_ptr 을 반환하기 때문에 reference counter가 증가한다.

 

위와 같은 방법 말고도 use_count나 expired 메서드를 이용해 weak_ptr가 메모리 공간을 가리키는지 알 수 있다.

 

 

 

'언어 > C++' 카테고리의 다른 글

Cpp 함수 (C++11 lambda, std::function)  (2) 2021.07.31
Cpp - 상속#2  (2) 2021.07.22
Cpp - 상속#1  (0) 2021.07.18
C++ (복사/이동) 생성자, 할당자, Rule of Three(Five)  (0) 2020.11.08
L_value와 R_value  (0) 2020.10.11