| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
Tags
- Overlapped Model
- 벨만-포드
- 비트마스킹
- Strongly Connected Component
- Behavior Design Pattern
- select 모델
- 게임 서버 아키텍처
- map
- Delete
- Prisma
- HTTP
- Lock-free Stack
- SCC
- Github
- 이분 탐색
- JavaScript
- trie
- 트라이
- 비트필드를 이용한 dp
- Spin Lock
- DP
- 강한 연결 요소
- 최소 공통 조상
- 그래프 탐색
- reference counting
- 자바스크립트
- Binary Lifting
- PROJECT
- 2-SAT
- ccw 알고리즘
Archives
- Today
- Total
dh_0e
[C++] Smart Pointer 본문
Smart Pointer 개요
- 기존 new, delete를 사용할 때 발생하는 여러 메모리 문제들(memory leak, nullptr issue, Dangling Pointer)을 방지하기 위해 만들어짐
- Java, C#에서는 Garbage Collector를 사용하여 이 문제를 해결하여 개발자의 생산성을 올려주었지만, 컴퓨터에 부하가 더해져 속도가 느려짐
- C++ 11 이후 추가되었으며, <memory> 헤더 파일을 선언하여 사용 가능
- 클래스로 만들어졌으며, 생산자에 포인터를 할당하고, 소멸자에서 이를 delete 해주면서 객체가 만들어질 때 메모리 할당을, 소멸할 때 메모리 해제를 자동으로 해주게끔 만들어 메모리 누수를 방지함
template<typename T>
class smart_pointer{
public:
smart_pointer(T* p): ptr(p) {}
~smart_pointer(){
delete ptr;
}
// 복사 금지: Double Free 방지
smart_pointer(const smart_pointer&) = delete;
smart_pointer& operator=(const smart_pointer&) = delete;
// 연산자 오버로딩
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
// 결과값이 여전히 포인터 타입이라면, 컴파일러는 거기에 다시 ->를 자동으로 붙여서 멤버에 접근
private:
T* ptr;
}
- unique_ptr을 class로 직접 구현해 보면 위와 같은 형태로 이루어져 있음
- 실제 코드는 훨씬 복잡하며 위 코드는 new로 할당한 단일 객체만 관리할 수 있고, new[]로 할당한 배열은 불가능
- smart_pointer<T[]> 형태에 대한 코드를 추가해야 함
- delete를 통한 수동 해제가 불가능함
- STL이라 원형은 std:: 생략 필
Smart Pointer 종류 및 사용법
1. shared_ptr
- 여러 포인터가 한 객체를 참조하여 관리할 수 있는 스마트 포인터
- 해당 값을 참조하고 있는 포인터가 몇 개인지 저장하고 있는 참조 횟수(reference count)가 있음
- 선언
- shared_ptr<T> ptr(new T(value)); // 메모리 할당 따로 2번 ( [객체], [RefCountBlock] )
- shared_ptr<T> ptr = make_shared<T>(value); // 메모리 할당 한 번에 ( [객체 | RefCountBlock] )
- 속도가 달라서 make_shared를 사용하는 게 좋음
- 선언

- 참조 횟수(reference count)
- ptr.use_count(); // main()이 종료되거나 참조 카운트가 0이 되어야 소멸자 작동
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> ptr=make_shared<int>(10);
printf("ptr: %d\n",*ptr);
printf("ref count: %d\n", ptr.use_count());
auto ptr2=ptr;
(*ptr2)++;
printf("ptr: %d\n",*ptr);
printf("ptr2: %d\n",*ptr2);
printf("ref count: %d\n", ptr2.use_count());
auto ptr3=ptr2;
(*ptr3)++;
printf("ptr: %d\n",*ptr);
printf("ptr2: %d\n",*ptr2);
printf("ptr3: %d\n",*ptr3);
printf("ref count: %d\n", ptr3.use_count());
return 0;
}

- shared_ptr이 관리하는 멤버 변수
- 다음과 같이 shared_ptr은 element_type, _Ref_count_base를 멤버 변수로 가지며, _Ref_count_base는 _Uses, _Weaks 두 개의 멤버 변수를 가지고 있음
- _Uses: shared pointer count
- _Weaks: weak pointer count
- 다음과 같이 shared_ptr은 element_type, _Ref_count_base를 멤버 변수로 가지며, _Ref_count_base는 _Uses, _Weaks 두 개의 멤버 변수를 가지고 있음


2. unique_ptr
- 하나의 포인터만이 객체를 참조하여 관리할 수 있는 스마트 포인터
- reference count(참조 횟수)가 1로 고정된 shared_ptr
- 복사 생성자를 지원하지 않으며, move() 함수를 통한 전달만 가능함
- 선언
- unique_ptr<T> ptr(new T(value));
- unique_ptr<T> ptr = make_unique<T>(value);
- make_unique는 속도 차이는 거의 없지만 가독성과 안정성 때문에 make_unique를 많이 씀
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> ptr=make_unique<int>(10);
printf("ptr: %d\n",*ptr);
auto ptr2=ptr; // error
return 0;
}
3. weak_ptr
- 약한 참조를 의미하며, shared_ptr를 사용할 때 순환 참조와 같은 문제에 대처하는 데 도움을 줌
- 순환 참조: 객체 안에 서로의 포인터를 잡고 있어서 서로가 끝나기 전까지 소멸하지 않는 데드락과 유사한 현상을 일으킴
- weak_ptr는 -> 연산자가 아예 없어서 객체에 직접 접근할 수 없음
- shared_ptr로 casting 후 접근해야 함
순환 참조 예시
#include <iostream>
#include <memory>
#include <string>
class Room; // 전방 선언
class Player {
public:
std::shared_ptr<Room> currentRoom; // 플레이어가 속한 방
~Player() { std::cout << "Player 소멸!\n"; }
};
class Room {
public:
std::shared_ptr<Player> owner; // 방의 주인인 플레이어
~Room() { std::cout << "Room 소멸!\n"; }
};
int main() {
{
auto p1 = std::make_shared<Player>();
auto r1 = std::make_shared<Room>();
// 서로를 가리킴 (순환 참조 발생)
p1->currentRoom = r1;
r1->owner = p1;
}
// 블록을 벗어나도 "소멸!" 메시지가 뜨지 않음 -> 메모리 누수 발생!
std::cout << "블록 종료\n";
return 0;
}
- weak_ptr은 shared_ptr과 유사하지만 가리키는 객체의 수명에 영향을 주지 않는 약한 참조를 하여 순환 참조를 피함
class Room {
public:
// shared_ptr 대신 weak_ptr 사용!
std::weak_ptr<Player> owner;
~Room() { std::cout << "Room 소멸!\n"; }
};
- 위와 같이 owner를 weak_ptr로 바꾸면 r1->owner = p1;을 해도 p1의 참조 횟수가 올라가지 않아 블록이 끝날 때 정상적으로 소멸함
// RefCountBlock(useCount(shared), weakCount(weak))
shared_ptr<Knight> spr = make_shared<Knight>();
weak_ptr<Knight> wpr = spr;
// weak_ptr로 shared_ptr를 받을 수 있음
bool expired = wpr.expired(); // 로 내가 참조하는 객체가 존재하는지 확인해야 함
shared_ptr<Knight> spr2 = wpr.lock(); // 이런 식으로 casting해서 사용할 수도 있음
// 존재하지 않으면 spr2에 nullptr이 들어감
- expired(): 생사 확인
- useCount == 0 인지를 체크
- true면 이미 객체가 메모리 해제된 상태를 말함
- lock(): 안전한 승격
- weak_ptr은 객체를 직접 조작할 수 없으므로 lock()을 통해 잠시 shared_ptr로 변신
- expired()로 확인한 직후 다른 스레드가 spr을 날려버릴 수 있기 때문에 lock()을 호출하여 useCount를 일시적으로 1 올려 안전한 객체 사용을 보장받음

- weak_ptr로 실제 객체를 쓰기 전에, 객체가 해제됐는지 알 수 없기 때문에 lock() 함수를 호출해서 객체가 아직 살아있는지 확인해야 함

'C++' 카테고리의 다른 글
| [C++] 콜백(Callback) 함수, using & typedef (0) | 2026.02.11 |
|---|---|
| [C++] lambda, 타입 추론(auto), 일반화 함수(template), Singleton in C++ (3) | 2025.08.05 |
| [C++] memset, isupper/islower (0) | 2025.02.13 |
| [C++] stringstream (2) | 2024.07.22 |
| [C++] max_element, min_element, find (0) | 2024.07.12 |
