스마트 포인터

  • #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은 이미 사라진 메모리에 소멸자로 접근하는 형태가 되기 때문에 에러가 발생하게 될 것이다.

+ Recent posts