본문 바로가기
언어/C++

[C++ STL] 1-3. 템플릿 - 스마트 포인터(smart pointer)

by 민-Zero 2020. 1. 14.

공부 내용을 정리하는 목적 이므로 참고용으로만 읽어 주시기 바랍니다.

틀린 부분에 대한 지적은 감사합니다.

1. 스마트 포인터(smart pointer)란?

자바의 경우 garbage collector를 통해 메모리를 관리하지만 c++은 사용자가 스스로 메모리를 할당 해제를 통해 관리해야 한다. c에서는 malloc, free로 메모리를 할당및 해제를 수행하고 c++은 new, delete를 사용한다. 이때 할당받은 메모리를 해제하지 않을경우 프로그램은 계속 사용하고 있는 메모리로 인지하고 해당 메모리를 사용하지 않는 메모리 누수(memory leak)가 발생한다. 이와 같은 메모리 누수를 방지하기 위해 스마트 포인터를 제공해준다. 스마트 포인터는 포인터 처럼 사용하는 클래스 템플릿으로 메모리를 자동으로 해제해 준다. 즉, delete를 자동으로 수행한다. 

스마트 포인터를 자체적으로 간단하게 만들어보면

이런 SmartPtr클래스를 사용하면 소멸자는 객체의 사용이 끝나면 자동으로 호출된다. 이곳에 delete가 존재해 메모리를 직접 해제하지 않아도 자동으로 해제되는 간단한 과정이다. 따라서 이 클래스를 템플릿으로 일반화시켜 어떤 객체에 대해서도 메모리를 할당받은 경우에 자동으로 해제를 해주는 것이다.

 

즉, 스마트 포인터는 new 키워드를 사용해 일반 포인터가 실제 메모리를 가리키도록 초기화한 후 기본 포인터를 스마트 포인터에 대입하여 사용한다. 이렇게 정의된 스마트 포인터가 수명이 다하면 소멸자를 통해 delete 키워드를 자동으로 사용해 메모리를 해제한다. 따라서 따로 메모리를 해제할 필요가 없게 된다.

2. 스마트 포인터 종류

스마트 포인터 기능은 c++11이후 부터  추가되었다. memory 헤더파일을 include해야 사용 가능하다.

 

① shared_ptr

shared_ptr은 어떤 하나의 객체를 참조하는 스마트 포인터의 개수를 참조하는 스마트 포인터이다. 이렇게 참조하고 있는 스마트 포인터의 개수를 참조 카운트(reference count)라고 한다. 참조 카운트란 해당 메모리를 참조하는 포인터가 몇개인지 나타내는 값으로 shared_ptr가 추가될때 1씩 증가하고 수명이 다하면 1씩 감소한다.

따라서 마지막 shared_ptr의 수명이 다하거나 main()함수가 종료되면 참조 카운트가 0이되어 delete를 사용하여 메모리를 자동으로 해제한다.

 

문법)

shared_ptr<객체> 스마트 포인터명(new 객체);

shared_ptr<객체> 스마트 포인터명 = make_shared<객체>(인수);

 

make_shared() 함수는 전달받은 인수를 사용하여 지정된 객체를 생성하고 생성된 객체를 참조하는 shared_ptr을 반환하기 때문에 해당 함수를 사용하면 shared_ptr 객체를 예외발생에 대해 안전하게 생성할 수 있다. 

 

예시를 통해 확인해 보면

use_count() 멤버 함수는 shared_ptr 객체가 현재 가리키고 있는 객체를 참조 중인 소유자의 수를 반환해주기 때문에 reference count와 동일한 값을 가진다.

shared_ptr s는 5라는 값을 가지는 int형 객체를 가리키게 하였다. 아직은 스마트 포인터 s 1개만 해당 객체를 가리키고 있으므로 reference count는 1이 출력되게 된다. 

다음 s2가 s가 가리키는 객체와 같은 값을 가리키는 shared_ptr이 되도록 auto를 사용하였다. 그리고 5라는 값을 가지는 객체의 값을 7로 증가시키고 확인해보면 이제 7이란 객체를 s와 s2가 가리키므로 reference count는 2가된다.

마지막으로 s3를 s2가 가리키는 객체를 가리키도록 하였다. 그럼 s, s2, s3 총 3개의 스마트포인터가 7이란 객체를 가리키고 있으므로 reference count가 3이 나오는것을 확인할 수 있다.

즉, shared_ptr은 객체가 가진 값의 변경 여부는 관계없이 객체를 가리키는 스마트 포인터의 개수를 참조하는 것을 확인할 수 있다. 

 

② unique_ptr

unique_ptr은 하나의 스마트 포인터만이 객체를 가리킬수 있도록 한다. 즉 shared_ptr과 다르게 reference count가 1을 넘길수 없다.

 

문법)

unique_ptr<객체> 스마트 포인터명(new 객체)

unique_ptr<객체> 스마트 포인터명 = make_unique<객체>(인수);

 

make_shared()함수 처럼 make_unique()함수를 사용하여 안전하게 인스턴스를 생성할 수 있다.

 

따라서 위와같이 동일한 객체를 다른 스마트 포인터 객체로 참조하려고 하면 에러가 발생한다.

단 하나의 스마트 포인터만 특정 객체를 가리킬수 있으므로 다른 스마트 포인터를 사용하고 싶다면 move()함수를 사용하여 가리키는 객체를 변경해야한다.

 

③ weak_ptr

weak_ptr은 하나 이상의 shared_ptr가 가리키는 객체를 참조할수 있지만 reference count를 늘리지않는 스마트 포인터이다. shared_ptr을 사용할때 발생할 수 있는 문제를 해결하기 위해 사용된다.

shared_ptr은 하나의 객체를 여러 스마트 포인터가 가리키고 reference count를 통해 동작한다. 그런데 만약 서로가 서로를 가리키는 shared_ptr을 가지게 되면 reference count가 0이 될 수가 없으므로 메모리가 해제되지 않는 순환 참조(circular reference)가 발생하게 된다. weak_ptr은 이러한 순환 참조를 제거하기 위해 사용된다.

 

댓글