개발자공부일기
C++ 스마트 포인터 본문
C++에서 new / delete를 직접 다루다 보면 메모리 누수, 이중 해제, 예외 안전성 문제가 자주 발생합니다.
이를 자동으로 처리해주는 도구가 바로 스마트 포인터(smart pointer)입니다.
스마트 포인터는 객체의 수명을 자동으로 관리하는 RAII(Resource Acquisition Is Initialization) 기법의 대표적인 예 입니다.
이 글에서는 unique_ptr, shared_ptr, weak_ptr의 동작을 아주 간단한 예제로 살펴보겠습니다.
RAII
RAII(Resource Acquisition Is Initialization)란,
"자원의 생명주기를 객체의 생명주기에 묶는 것”을 의미합니다.
스마트 포인터는 이 원칙을 그대로 따릅니다.
- 생성 시 → new로 자원 획득
- 소멸 시 → delete로 자동 해제
예외가 발생하더라도 스코프를 벗어나는 순간 자원이 안전하게 반환됩니다.
테스트용 Person 클래스
#include <iostream>
#include <memory>
using namespace std;
class Person {
public:
string name;
Person(string n) : name(n) {
cout << name << " 생성됨\n";
}
~Person() {
cout << name << " 소멸됨\n";
}
void sayHello() {
cout << "안녕하세요, 저는 " << name << "입니다.\n";
}
};
이 Person 클래스는 생성자와 소멸자에서 로그를 출력합니다.
객체가 생성되고 소멸되는 순간을 눈으로 확인할 수 있습니다.
1. unique_ptr — 단독 소유권 (Single Ownership)
int main() {
{
unique_ptr<Person> p1 = make_unique<Person>("길동");
p1->sayHello();
// 소유권 이동 (복사 불가, 이동만 가능)
unique_ptr<Person> p2 = move(p1);
if (!p1) cout << "p1은 이제 비어 있음\n";
p2->sayHello();
} // 스코프 종료 → p2가 자동으로 delete 호출
cout << "----- unique_ptr 예제 끝 -----\n";
}
출력 예시
길동 생성됨
안녕하세요, 저는 길동입니다.
p1은 이제 비어 있음
안녕하세요, 저는 길동입니다.
길동 소멸됨
----- unique_ptr 예제 끝 -----
해설
- make_unique로 객체 생성 → delete를 직접 호출하지 않아도 자동 해제
- unique_ptr은 하나의 소유자만 존재
- p1에서 p2로 move(이동) 해야만 소유권을 넘길 수 있음
- 스코프를 벗어날 때 자동으로 delete 호출됨
2. shared_ptr — 공유 소유권 (Shared Ownership)
int main() {
{
shared_ptr<Person> a = make_shared<Person>("철수");
{
shared_ptr<Person> b = a; // 같은 객체를 공유
cout << "참조 횟수: " << a.use_count() << endl;
} // b 소멸 → 참조 횟수 감소
cout << "참조 횟수: " << a.use_count() << endl;
} // 마지막 shared_ptr이 사라질 때 객체 delete
cout << "----- shared_ptr 예제 끝 -----\n";
}
출력 예시
철수 생성됨
참조 횟수: 2
참조 횟수: 1
철수 소멸됨
----- shared_ptr 예제 끝 -----
해설
- 내부적으로 참조 카운트(use_count) 를 관리
- 카운트가 0이 되는 순간 객체 자동 해제
- 여러 스마트 포인터가 하나의 객체를 안전하게 공유할 수 있음
3. weak_ptr — 약한 참조 (Non-owning Reference)
int main() {
weak_ptr<Person> wp;
{
auto sp = make_shared<Person>("민지");
wp = sp; // 약한 참조 연결
cout << "use_count: " << sp.use_count() << endl;
if (auto s = wp.lock()) { // lock()으로 임시 소유권 획득
s->sayHello();
}
} // sp 소멸됨 → weak_ptr만 남음
if (wp.expired()) {
cout << "객체는 이미 소멸됨\n";
}
cout << "----- weak_ptr 예제 끝 -----\n";
}
출력 예시
민지 생성됨
use_count: 1
안녕하세요, 저는 민지입니다.
민지 소멸됨
객체는 이미 소멸됨
----- weak_ptr 예제 끝 -----
해설
- weak_ptr은 객체를 소유하지 않음 (참조 카운트에 포함되지 않음)
- lock()으로 잠시 shared_ptr로 승격해 안전하게 접근 가능
- 객체가 이미 삭제되었으면 expired()로 확인 가능
- 순환 참조 방지 용도로 자주 사용됨 (특히 트리, 그래프 구조에서)
스마트 포인터 | 소유권 | 복사 | 참조 카운트 | 주요 용도 |
unique_ptr | 단독 | 불가 (move만 가능) | X | 기본 선택, RAII 자원 관리 |
shared_ptr | 공유 | 가능 | O | 여러 소유자 필요 시 |
weak_ptr | 없음 | 가능 | O(약한 참조만) | 순환 참조 방지, 상태 확인 |