스마트 포인터란
포인터처럼 사용하는 클래스 템플릿
사용이 끝난 메모리를 자동으로 해제해 메모리 누수를 방지한다.
스마트 포인터 유형
- unique_ptr
- shared_ptr
- weak_ptr
unique_ptr
하나의 스마트 포인터만이 특정 객체를 소유할 수 있는 스마트 포인터
해당 객체의 소유권을 가지고 있을 때만 소멸자가 해당 객체를 삭제할 수 있다.
unique_ptr 인스턴스는 move() 함수를 통해 소유권 이전이 가능하지만 복사할 수는 없다.
소유권이 이전되면 이전 unique_ptr 인스턴스는 해당 객체를 소유하지 않게 재설정된다.
// 문법
unique_ptr<객체> 변수명(new 객체);
unique_ptr<객체> 변수명 = make_unique<객체>(인수);
#include <iostream>
#include <memory>
usingnamespace std;
int main()
{
unique_ptr<int> ptr_1(new int(10)); // int형 unique_ptr 선언과 동시에 초기화
unique_ptr<int> ptr_2 = move(ptr_1); // ptr_1에서 ptr_2로 소유권 이전
unique_ptr<int> ptr_3 = ptr_2; // ERROR! 대입을 이용한 복사는 에러를 발생
ptr_1.reset(); // ptr_1이 가리키고 있는 메모리 영역 삭제
return 0;
}
영역을 벗어나면 개체가 자동으로 삭제되니 delete를 통해 메모리를 해제할 필요가 없다.
shared_ptr
하나의 특정 객체를 참조하는 스마트 포인터가 몇 개인지를 참조하는 스마트 포인터
특정 객체에 새로운 shared_ptr가 생길 때마다 참조 개수(Reference count)가 증가하며 소멸될 때마다 감소한다.
마지막 shared_ptr의 수명이 다해 참조 횟수가 0이 되면 delete 키워드를 사용해 자동으로 메모리를 해제한다.
// 문법
shared_ptr<객체> 변수명(new 객체);
shared_ptr<객체> 변수명 = make_shared<객체>(인수);
#include <iostream>
#include <memory>
usingnamespace std;
class Person
{
private:
string name_;
int age_;
public:
Person(const string& name, int age)
: name_(name), age_(age)
{ cout << "생성자 호출" << endl; }
~Person() { cout << "소멸자 호출" << endl; }
}
int main()
{
shared_ptr<Person> shin = make_shared<Person>("짱구", 5);
cout << "소유자 수: " << shin.use_count() << endl;
shared_ptr<Person> kim = shin;
cout << "소유자 수: " << shin.use_count() << endl;
kim.reset();
cout << "소유자 수: " << shin.use_count() << endl;
}
실행 결과
생성자 호출
소유자 수: 1
소유자 수: 2
소유자 수: 1
소멸자 호출
순환 참조
서로 상대방을 가리키는 shared_ptr가 있을 경우 참조 개수가 0이 되지 않아 메모리가 해제되지 않는 경우가 있다.
#include <iostream>
#include <memory>
class Person
{
private:
string name_;
int age_;
shared_ptr<Person> mate_;
public:
Person(const string& name, int age)
: name_(name), age_(age)
{ cout << "생성자 호출" << endl; }
~Person() { cout << "소멸자 호출" << endl; }
void set_mate(shared_ptr<Person> m) { mate_ = m; }
}
int main()
{
shared_ptr<Person> shin = make_shared<Person>("짱구", 5);
shared_ptr<Person> kim = make_shared<Person>("철수", 5);
shin->set_mate(kim);
kim->set_mate(shin);
}
실행 결과
생성자 호출
생성자 호출
객체 shin이 해제되려면 shin을 가리키고 있는 shared_ptr의 참조 개수가 0이 되어야 한다.
즉 shin을 가리키는 객체 kim이 해제되어야 하는데 kim 또한 이를 가리키고 있는 객체 shin이 해제되어야 한다.
그렇기 때문에 실행 결과처럼 소멸자가 호출되지 않는다.
이를 해결하기 위해 나온 것이 weak_ptr다.
weak_ptr
shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공하지만 참조 개수에 포함되지 않는 스마트 포인터
shared_ptr의 객체를 참조할 때 참조 개수를 올리진 않지만 shared_ptr의 weak reference count는 증가시킨다. 이는 객체 소멸에 전혀 관여하지 않는다.
weak_ptr는 shared_ptr로부터의 복사 생성, 대입 연산으로만 할당이 가능하다.
// 문법
weak_ptr<객체> 변수명(share_ptr 포인터)
weak_ptr<객체> 변수명 = shared_ptr 포인터
// 예시
shared_ptr<int> s_ptr = make_shared<int>(10);
weak_ptr<int> w1_ptr(s_ptr);
weak_ptr<int> w2_ptr = s_ptr;
weak_ptr는 할당받은 자원을 사용하려면 lock() 함수를 통해 shared_ptr 유형으로 convert 해야 한다.
할당받은 자원의 shared_ptr의 참조 개수가 0이 되어 해제되면 expire 돼서 사용할 수 없게 된다.
expire 된 상태에 lock() 함수를 호출하면 empty 상태의 shared_ptr가 반환된다.
#include <iostream>
#include <memory>
usingnamespace std;
class Person
{
private:
string name_;
int age_;
shared_ptr<Person> mate_;
public:
Person(const string& name, int age)
: name_(name), age_(age)
{ cout << "생성자 호출" << endl; }
~Person() { cout << "소멸자 호출" << endl; }
void set_mate(shared_ptr<Person> m) { mate_ = m; }
}
int main()
{
shared_ptr<Person> shin = make_shared<Person>("짱구", 5);
shared_ptr<Person> kim = make_shared<Person>("철수", 5);
weak_ptr<Person> w1_shin(shin); // 복사 생성자로 객체 생성
weak_ptr<Person> w2_shin = wc_shin; // 대입 연산자로 객체 생성
w1_shin->set_mate(kim); // ERROR! weak_ptr는 직접적으로 자원에 접근할 수 없음
if (w1_shin.expired() == false) // shared_ptr의 참조 개수가 0이 아닌지 확인
{
shared_ptr<Person> c_shin = w1_shin.lock(); // shared_ptr로 변환
c_shin->set_mate(kim);
} // c_shin은 이 영역을 벗어나면서 해제됨
}
'c++' 카테고리의 다른 글
[자료구조] 스택 구현하기 - Stack (0) | 2023.06.13 |
---|---|
[C++] 포인터와 참조자 - Pointer, Reference (0) | 2023.06.08 |
[C++] 가상 함수 - virtual (0) | 2023.06.08 |
[C++] 객체 지향 프로그래밍(OOP, Object Oriented Programming) (0) | 2023.06.08 |
[C++] Call by value, Call by reference (0) | 2023.06.07 |
댓글