스마트 포인터(Smart Pointer)는 메모리 누수(Memory Leak)와 댕글링 포인터(Dangling Pointer) 문제를 방지하기 위해 도입된 C++의 기능입니다.
C++의 표준 스마트 포인터는 RAII(Resource Acquisition Is Initialization) 원칙을 적용하여 객체의 생명 주기를 자동으로 관리합니다.
✅ 스마트 포인터 종류
unique_ptr: 단독 소유권을 가짐. 복사 불가능. std::move를 이용해 이동 가능.shared_ptr: 참조 카운트(Reference Count)를 사용하여 여러 개의 스마트 포인터가 동일한 객체를 공유.weak_ptr: shared_ptr을 참조하되, 참조 카운트를 증가시키지 않아 순환 참조(Circular Reference) 문제를 방지.✅ 스마트 포인터를 사용하면
delete 호출 필요 없음.Knight 클래스 (공격 기능 포함)class Knight
{
public:
Knight() { cout << "Knight 생성" << endl; }
~Knight() { cout << "Knight 소멸" << endl; }
void Attack()
{
if (_target.expired() == false) // `weak_ptr`이 유효한지 확인
{
shared_ptr<Knight> sptr = _target.lock(); // `weak_ptr`을 `shared_ptr`로 변환
sptr->_hp -= _damage; // 대상 HP 감소
cout << "HP: " << sptr->_hp << endl;
}
}
public:
int _hp = 100;
int _damage = 10;
weak_ptr<Knight> _target; // `shared_ptr`의 순환 참조를 방지하기 위해 `weak_ptr` 사용
};
✅ weak_ptr을 사용한 순환 참조 방지
_target을 shared_ptr 대신 weak_ptr로 선언하여 순환 참조 문제 해결.expired()로 참조 대상이 유효한지 확인 후, lock()을 통해 shared_ptr로 변환하여 사용.RefCountBlock 클래스 (참조 카운트 관리)class RefCountBlock
{
public:
int _refCount = 1; // `shared_ptr`에 의해 참조된 횟수
int _weakCount = 1; // `weak_ptr`에 의해 참조된 횟수
};
✅ 참조 카운트 관리
_refCount: shared_ptr이 객체를 참조하는 횟수._weakCount: weak_ptr이 객체를 참조하는 횟수.SharedPtr 템플릿 클래스 (사용자 정의 shared_ptr)template<typename T>
class SharedPtr
{
public:
SharedPtr() { }
SharedPtr(T* ptr) : _ptr(ptr)
{
if (_ptr != nullptr)
{
_block = new RefCountBlock(); // 참조 카운트 블록 생성
cout << "RefCount : " << _block->_refCount << endl;
}
}
SharedPtr(const SharedPtr& sptr) : _ptr(sptr._ptr), _block(sptr._block)
{
if (_ptr != nullptr)
{
_block->_refCount++; // 참조 카운트 증가
cout << "RefCount : " << _block->_refCount << endl;
}
}
void operator=(const SharedPtr& sptr)
{
_ptr = sptr._ptr;
_block = sptr._block;
if (_ptr != nullptr)
{
_block->_refCount++;
cout << "RefCount : " << _block->_refCount << endl;
}
}
~SharedPtr()
{
if (_ptr != nullptr)
{
_block->_refCount--; // 참조 카운트 감소
cout << "RefCount : " << _block->_refCount << endl;
if (_block->_refCount == 0) // 참조가 모두 해제되면 객체 삭제
{
delete _ptr;
delete _block;
cout << "Delete Data" << endl;
}
}
}
public:
T* _ptr = nullptr; // 원본 객체 포인터
RefCountBlock* _block = nullptr; // 참조 카운트 블록
};
✅ 사용자 정의 shared_ptr
RefCountBlock을 이용해 참조 카운트 관리.main 함수int main()
{
shared_ptr<Knight> k1 = make_shared<Knight>();
// k1 [ ]
// k2 [ ]
{
shared_ptr<Knight> k2 = make_shared<Knight>();
k1->_target = k2; // k1이 k2를 참조
k2->_target = k1; // k2가 k1을 참조 (순환 참조 발생)
}
k1->Attack();
// unique_ptr : 복사가 불가능한 스마트 포인터
unique_ptr<Knight> uptr = make_unique<Knight>();
unique_ptr<Knight> uptr2 = std::move(uptr); // 이동 연산
return 0;
}
✅ 순환 참조 문제 발생
k1과 k2가 서로를 shared_ptr로 참조하여 순환 참조 문제 발생._target을 weak_ptr로 변경하여 참조 카운트 증가를 방지.✅ unique_ptr 활용
unique_ptr<Knight> uptr = make_unique<Knight>();std::move(uptr);을 사용하여 unique_ptr 이동 가능.| 스마트 포인터 | 특징 | 장점 | 단점 |
|---|---|---|---|
shared_ptr | 참조 카운트를 이용한 공유 | 메모리 자동 관리 | 순환 참조 문제 발생 가능 |
weak_ptr | shared_ptr를 참조하지만 참조 카운트 증가 없음 | 순환 참조 방지 | lock()을 통해 shared_ptr로 변환 필요 |
unique_ptr | 단독 소유권, 복사 불가 | 자동 메모리 해제, 성능 최적화 | 복사 불가능 (이동 연산만 가능) |
class Knight
{
public:
weak_ptr<Knight> _target; // weak_ptr을 사용하여 순환 참조 방지
};
✅ weak_ptr 사용 이유
shared_ptr을 사용하면 객체가 해제되지 않는 순환 참조 문제 발생.weak_ptr은 참조 카운트를 증가시키지 않음 → 메모리 누수 방지.unique_ptr 사용 예제unique_ptr<Knight> uptr = make_unique<Knight>(); // 객체 생성
unique_ptr<Knight> uptr2 = std::move(uptr); // 이동 연산
✅ unique_ptr의 특징
std::move로 이동만 가능).