C++ 프로그램에서 new 키워드를 사용하여 동적으로 할당받은 메모리는, 반드시 delete 키워드를 사용하여 해제해야 하는데, 만약 해제하지 않고 넘어갈 경우에 메모리 누수 (Memory Leak) 문제가 발생해서 프로그램의 안정성을 보장받을 수 없게 된다. 스마트 포인터는 클래스 템플릿으로서 사용이 끝난 메모리를 자동으로 해제해주어 메모리 누수 문제가 일어나지 않도록 해준다. 동작하는 방법은 기본 포인터 (Raw Pointer)가 실제 메모리를 가리키도록 초기화한 후에, 기본 포인터를 스마트 포인터에 대입하여 사용된다.
스마트 포인터의 종류
C++ 11 표준 이전에도 auto_ptr 이라는 스마트 포인터로 작업을 처리했었는데, 현재 모던 C++ 이라 불리우는 C++ 11 이상의 표준에 대해서는 auto_ptr이 사라지고 새로운 스마트 포인터들이 제공되었다. 그 종류는 대표적으로 unique_ptr, shared_ptr, weak_ptr 가 있다.
Unique Pointer
하나의 스마트 포인터만이 특정 객체를 소유할 수 있도록 설계되었다, 해당 객체의 소유권을 가지고 있을 때만, 소멸자가 해당 객체를 삭제할 수 있다. 인스턴스는 move() 멤버 함수를 통해 소유권을 이전할 수는 있지만, 복사할 수는 없다. 소유권이 이전되면, 이전 인스턴스는 더이상 해당 객체를 소유하지 않게 재설정된다.
아래는 간단한 unique_ptr의 사용 예이다.
#include <iostream>
using namespace std;
int main()
{
//Unique Pointer 초기화와 함께 int형 값 10으로 동적할당
unique_ptr<int> ptr1(new int(10));
//auto 키워드로 ptr2는 ptr1의 타입을 추론해 받게된다.
//move 키워드는 ptr1에서 ptr2로 메모리의 소유권을 이전하기 위해 사용된다.
auto ptr2 = move(ptr1);
//애초에 ptr1이 소멸되어 접근이 불가하다.
//대입 연산자를 이용한 복사는 오류를 발생시킨다.
unique_ptr<int> ptr3 = ptr1; // ERROR
if (ptr1 == nullptr)
{
cout << "I'm Dead. Call ptr2 instead." << endl;
cout << ptr1 << endl;
}
cout << *ptr2 << endl;
//reset 함수로 메모리 영역을 삭제할 수 있다.
ptr2.reset();
ptr1.reset();
return 0;
}
ptr1에서 ptr2로 소유권이 이전되면서 ptr1은 자동으로 소멸된다. 그래서 nullptr과 비교연산을 했을 때 참이기 떄문에, 죽었다는 메시지와 ptr1의 주소값을 보여주게 되었다. ptr2로 메모리가 잘 이전된 것을 확인하기위해 ptr2의 값을 보는 코드에서도 10이 정확히 잘 출력되었다. C++ 14 표준 이후부터 제공되는 make_unique() 함수를 사용하면 unique_ptr 인스턴스를 안전하게 생성할 수 있다. make_unique() 함수는 전달받은 인수를 사용해 지정된 타입의 객체를 생성하고, 생성된 객체를 가리키는 unique_ptr을 반환해준다. 이 함수를 사용하면 예외 발생에 대해 안전하게 대처할 수 있다.
아래는 make_unique()를 사용하는 예제 코드이다.
#include <iostream>
using namespace std;
class HGT
{
public:
string name = "";
HGT() { cout << "생성" << endl; }
HGT(string _name) { name = _name; cout << "생성" << endl; }
~HGT() { cout << "소멸" << endl; }
void HelloWorld() { cout << name <<" : Hello World!" << endl; }
};
int main()
{
//HGT 클래스를 생성
unique_ptr<HGT> hgt_ptr = make_unique<HGT>("홍규태");
hgt_ptr->HelloWorld();
return 0;
}
make_unique 함수를 사용해 HGT 클래스의 인스턴스가 생성되었다. 그 인스턴스는 hgt_ptr의 소유이다. 위의 예제 코드에서 HGT 객체를 가리키는 unique_ptr 인스턴스 hgt_ptr은 자동으로 소멸된다.
Shared Pointer
하나의 특정 객체를 참조하는 스마트 포인터가 총 몇개인지 를 참조할 수 있도록 설계되었다. 참조하고 있는 스마트 포인터의 개수를 참조 횟수(Reference Count)라고 하며, 참조 횟수는 특정 객체에 새로운 shared_ptr이 추가될 때마다 1씩 증가하고, 추가되었던 shared_ptr이 해제되어 참조 카운트가 0이 되면 delete 가 자동으로 진행되어 메모리를 자동으로 해제해준다.
아래는 간단한 shared_ptr의 사용 예이다.
#include <iostream>
using namespace std;
int main()
{
shared_ptr<double> ptr1(new double(123.456));
cout << ptr1.use_count() << endl;
auto ptr2(ptr1);
cout << ptr2.use_count() << endl;
auto ptr3(ptr2);
cout << ptr3.use_count() << endl;
return 0;
}
하나의 메모리에 여러 shared_ptr가 붙었다. 붙은 shared_ptr 갯수만큼 참조 카운트가 올라가고 그 갯수는 use_count() 함수로 알아낼 수 있다. make_shared() 함수를 통해 shared_ptr의 인스턴스를 안전하게 만들 수 있다. make_shared() 함수는 전달받은 인수를 사용해 지정된 타입의 객체를 생성하고, 생성된 객체를 가리키는 shared_ptr을 반환해준다. 이 함수도 예외 발생에 대해 안전하다.
아래는 make_shared()의 사용 예이다.
#include <iostream>
using namespace std;
class Monster {
public:
Monster() { cout << "생성" << endl; }
~Monster() { cout << "소멸" << endl; }
};
int main()
{
shared_ptr<Monster> mst_ptr1 = make_shared<Monster>();
cout << mst_ptr1.use_count() << endl;
auto mst_ptr2 = mst_ptr1;
cout << mst_ptr1.use_count() << endl;
mst_ptr2.reset();
cout << mst_ptr1.use_count() << endl;
return 0;
}
Weak Pointer
하나 이상의 shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공하지만, 참조 카운트에 포함되지 않도록 설계되었다. shared_ptr은 참조 카운트를 기반으로 동작하는 스마트 포인터이다. 만약에 서로가 상대를 가르키는 shared_ptr을 가지고 있다면, 참조 횟수는 절대 1 이하로 내려가지 않기 때문에, 순환 참조(Circular Reference)가 발생하는데 weak_ptr은 이를 예방하기 위해서 사용한다.
아래는 weak_ptr의 사용 예이다.
#include <iostream>
using namespace std;
class Monster {
public:
//shared_ptr로 선언할 경우 순환 참조 발생
//weak_ptr로 선언하여 순환 참조를 예방함
weak_ptr<Monster> otherMonster;
Monster() { cout << "생성" << endl; }
~Monster() { cout << "소멸" << endl; }
};
int main()
{
//철수와 민수에 대한 shared_ptr을 선언
shared_ptr<Monster> chul_su = make_shared<Monster>();
shared_ptr<Monster> min_su = make_shared<Monster>();
cout << "철수 참조 카운트 : " << chul_su.use_count() << endl;
cout << "민수 참조 카운트 : " << min_su.use_count() << endl;
chul_su->otherMonster = min_su;
min_su->otherMonster = chul_su;
cout << "철수 참조 카운트 : " << chul_su.use_count() << endl;
cout << "민수 참조 카운트 : " << min_su.use_count() << endl;
return 0;
}
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ 추상 클래스 / 순수 가상 함수 (Pure Virtual Function) (0) | 2022.07.28 |
---|---|
C++ 클래스 접근 제한자 (Access Modifier) (0) | 2022.07.27 |
C++ 바이트 패딩 (Byte Padding) (0) | 2022.07.26 |
C++ 참조 대상 수 (Reference Counting) (0) | 2022.07.24 |
C++ 순환 참조 (Circular Dependency) & 데드락 (0) | 2022.07.24 |