스마트 포인터
- #include < memory>
- c++11부터 지원
- c++에서는 "메모리 누수"로부터 프로그램의 안정성을 보장하기 위해 스마트 포인터를 제공한다.
- 스마트 포인터는 생성하면 힙 메모리에 올라간다. (동적할당한다고 보면 됨)
- 스마트 포인터는 사용이 끝난 메모리를 자동으로 해제해 준다.
스마트 포인터의 동작
보통 new를 사용해 기본 포인터가 실제 메모리를 가리키도록 초기화한 후에, 기본 포인터를 스마트 포인터에
대입하여 사용한다.
➡️ new가 반환하는 주소값을 스마트 포인터에 대입하면 따로 메모리를 해제할 필요가 없다!!
스마트 포인터의 종류 3가지
- unique_ptr
- shared_ptr
- weak_ptr
shared_ptr
- 하나의 특정 객체를 참조하는 스마트 포인터가 총 몇개인지를 참조하는 스마트 포인터
- 원시 포인터 하나를 여러 소유자에게 할당하려고 할 때 사용
- 참조하고 있는 스마트 포인터의 갯수 = 참조 횟수 ( reference count )
- 참조 횟수는 특정 객체에 새로운 shared_ptr이 추가될 때마다 +1 증가 / 수명이 다할 때마다 -1 감소
- 마지막 shared_ptr의 수명이 다하여 참조 횟수가 0이 되면 delete키워드를 사용해 메모리를 자동으로 해제
-> 원시 포인터는 모든 shared_ptr소유자가 해제될 때까지 메모리에서 사라지지 않는다.
shared_ptr 사용 예시
#include<iostream>
#include<memory>
using namespace std;
int main(){
shared_ptr<int> ptr01(new int(5)); // int형 shared_ptr인 ptr01을 선언하고 초기화함
cout << ptr01.use_count() << endl; // 1
auto ptr02(ptr01); // 복사 생성자를 이용한 초기화
cout << ptr01.use_count() << endl; // 2
auto ptr03 = ptr01; // 대입을 통한 초기화
cout << ptr01.use_count() << endl; // 3
cout << *ptr01 << endl; // shared_ptr에 저장되어 있는 값인 5 출력됨
}
make_shared ( shared_ptr 스마트포인터를 안전하게 생성하는 방법 )
shared_ptr<Book> b3 = new Book("CC");
shared_ptr<Book> b4 = b3;
위와 같이 복사생성자로 b4 객체를 만들면 주소값만 복사하는 얕은 복사가 이루어지므로 메모리 해제 시
같은 곳을 해제하게 되어 오류가 발생할 것이다.
이럴 경우 어떻게 동기화시킬 수 있을까? 이때 사용해야 할 것이 make_shared이다.
make_shared
- make_shared()를 이용하면 shared_ptr 인스턴스를 안전하게 생성할 수 있다.
- make shared는 객체와 참조 카운터를 위한 메모리를 할당한다.
- shared_ptr를 생성할 때는 make_shared를 사용해서 선언하도록 하자!!
- 여러 개의 스마트포인터에서 동일한 메모리를 가리킬 때 사용해야 한다.
- 지정된 타입의 객체를 생성하고, 생성된 객체를 가리키는 shared_ptr을 반환한다.
- 위와 같은 문제에 대해 안전하게 대처 가능하다.
make_shared 사용 예시
예시1)
shared_ptr<Person> hong = make_shared<Person>("길동", 29);
cout << hong.use_count() << endl; // 1
auto han = hong;
cout << hong.use_count() << endl; // 2
han.reset();
cout << hong.use_count() << endl; // 1
class Person 객체를 가리키는 hong이라는 shared_ptr 변수를 make_shared()를 통해 생성한 것이다.
예시2)
#include<iostream>
#include<memory>
using namespace std;
int main(){
shared_ptr<int> s1 = make_shared<ibnt>(10);
cout << s1.use_count() << endl; // 1
{
shared_ptr<int> s2 = s1;
cout << s1.use_count() << " " << s2.use_count() << endl; // 2 2
}
cout << s1.use_count() << endl; // 1
return 0;
}
중간에 {}로 임시로 만든 범위가 끝나면 shared_ptr s2는 지역변수라서 자동으로 사라진다.
이때 스마트 포인터이기 때문에 사용자가 delete를 해주지 않아도 자동으로 delete로 소멸자를 호출해준다.
또한 make_shared로 shared_ptr 인스턴스를 생성했기 때문에 객체를 복사해서 만든 새로운 객체에서
delete로 메모리를 해제해줘도 얕은 복사로 인한 문제가 발생하지 않는다.
그래서 블록을 빠져나온 후 s1.use_count()에서 카운트가 준 것이다.
예시3)
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class Monster
{
private:
string name_;
float hp_;
float damage_;
public:
Monster(const string& name, float hp, float damage); // 생성자 선언
~Monster(){
cout << "메모리 해제" << endl;
}
void PrintMonsterInfo();
};
Monster::Monster(const string& name, float hp, float damage)
{
name_ = name;
hp_ = hp;
damage_ = damage;
cout << "생성자 호출" << endl;
}
void Monster::PrintMonsterInfo()
{
cout << "몬스터 이름 : " << name_ << endl;
cout << "체력 : " << hp_ << endl;
cout << "공격력 : " << damage_ << endl;
}
int main()
{
shared_ptr<Monster> dragon = make_shared<Monster>("드래곤", 5000.f, 500.f);
cout << "현재 소유자 수 : " << dragon.use_count() << endl; // 1
auto dragon2 = dragon; // 2개의 스마트포인터가 동일한 메모리를 가리키게 됨
cout << "현재 소유자 수 : " << dragon.use_count() << endl; // 2
dragon2->PrintMonsterInfo();
cout << "메모리 해제" << endl;
dragon2.reset(); // dragon2 해제
cout << "현재 소유자 수 : " << dragon.use_count() << endl; // 1
}
make_shared를 이용해 스마트포인터를 생성했고, make_shared를 이용해 선언한 스마트포인터를
dragon2가 복사했기 때문에 dragon2 객체를 해제해도 dragon객체의 메모리를 해제하는데 문제되지 않는다.
만약 make_shared를 사용하지 않고 그냥 shared_ptr<Monster> dragon("", , )이렇게 스마트 포인터를 선언해서
이 스마트포인터를 dragon2가 복사했다면 dragon2와 dragon이 같은 주소의 메모리를 해제하기 때문에
dragon2의 소멸자를 호출해서 메모리를 해제하면 나중에 dragon객체의 소멸자를 호출할 때
dragon은 이미 사라진 메모리에 소멸자로 접근하는 형태가 되기 때문에 에러가 발생하게 될 것이다.
'C,C++' 카테고리의 다른 글
C++ 클래스, this포인터, 기본생성자, 디폴트생성자, 복사생성자 (0) | 2022.04.10 |
---|---|
C++ enum ( 열거형 자료형 ) (0) | 2022.04.10 |
C++ 범위 지정 연산자(::)와 namespace (0) | 2022.04.10 |
C++ 멤버 초기화 리스트 (0) | 2022.04.10 |
C++ 가상 함수(virtual function) (0) | 2022.04.09 |