weak_ptr 정리
1. weak_ptr이란?
weak_ptr은 shared_ptr과 함께 사용하는 스마트 포인터로, 객체를 참조하지만 레퍼런스 카운트에는 영향을 주지 않는 포인터입니다.
- shared_ptr → 객체의 소유권을 가짐, 레퍼런스 카운트 증가
- weak_ptr → 객체를 관찰만 함, 레퍼런스 카운트 증가 X
즉, weak_ptr은 객체의 생명주기를 유지하지 않습니다.
예:
객체
↑ ↑ ↑
SP1 SP2 WP1
이 경우:
- shared_ptr 2개 → 레퍼런스 카운트 = 2
- weak_ptr 1개 → 레퍼런스 카운트에 영향 없음
따라서 shared_ptr이 모두 사라지면 객체는 삭제되고, weak_ptr은 더 이상 유효하지 않은 상태가 된다.
2. weak_ptr이 필요한 이유
shared_ptr은 여러 객체가 하나의 객체를 공유할 수 있게 해준다.
하지만 문제는 순환 참조(Circular Reference)이다.
예:
Kitty shared_ptr → Nabi
Nabi shared_ptr → Kitty
서로가 서로를 shared_ptr로 가리키면:
Kitty
↑ ↓
Nabi
레퍼런스 카운트가 계속 1 이상으로 유지된다.
결과:
- 외부 shared_ptr이 모두 제거됨
- 하지만 서로가 서로를 참조하고 있음
- 레퍼런스 카운트가 0이 되지 않음
- 소멸자가 호출되지 않음
- 메모리 릭 발생
이를 해결하기 위해 weak_ptr을 사용한다.
3. weak_ptr 동작 방식
weak_ptr은 shared_ptr을 참조하지만 소유권을 가지지 않는다.
예:
std::shared_ptr<Cat> cat = std::make_shared<Cat>();
std::weak_ptr<Cat> weakCat = cat;
상태:
Cat Object
shared_ptr
↓
Cat Object
↑
weak_ptr
레퍼런스 카운트:
shared_ptr : 1
weak_ptr : 영향 없음
4. weak_ptr 사용 방법
weak_ptr은 직접 객체에 접근할 수 없다.
반드시 lock()을 통해 shared_ptr로 변환해야 한다.
예:
auto sharedCat = weakCat.lock();
결과:
- 객체가 존재하면 → 새로운 shared_ptr 반환
- 객체가 삭제되었으면 → 빈 shared_ptr 반환
예:
if(sharedCat)
{
sharedCat->Speak();
}
else
{
std::cout << "객체가 존재하지 않음";
}
5. weak_ptr의 객체 생존 확인 방법
방법 1: lock()
가장 일반적인 방법.
auto ptr = weakPtr.lock();
if(ptr)
{
ptr->Function();
}
객체가 살아있는 경우에만 사용 가능하다.
방법 2: expired()
객체가 삭제되었는지 확인한다.
if(weakPtr.expired())
{
// 객체 삭제됨
}
- true → 객체 없음
- false → 객체 존재
6. shared_ptr 순환 참조 해결
기존 코드:
class Cat
{
std::shared_ptr<Cat> friendCat;
};
문제:
Kitty
↓
Nabi
↓
Kitty
순환 참조 발생.
해결:
class Cat
{
std::weak_ptr<Cat> friendCat;
};
변경 후:
Kitty
↓
weak_ptr
↓
Nabi
weak_ptr은 레퍼런스 카운트를 증가시키지 않기 때문에 순환 참조가 사라진다.
7. unique_ptr을 멤버 변수로 사용할 때 주의점
unique_ptr은 Exclusive Ownership을 보장한다.
즉:
하나의 객체
↓
하나의 unique_ptr
만 가능하다.
따라서 기본 복사가 불가능하다.
예:
class Cat
{
std::unique_ptr<int> value;
};
이때:
Cat a;
Cat b = a;
는 컴파일 오류 발생.
이유:
복사를 하면:
Cat A
unique_ptr
↓
int 객체
Cat B
unique_ptr
↓
같은 int 객체
가 되어 unique_ptr의 소유권 규칙을 위반한다.
그래서 컴파일러는:
- Copy Constructor 삭제
- Copy Assignment 삭제
를 자동으로 수행한다.
8. unique_ptr 클래스에서 직접 복사 구현하기
정말 복사가 필요하다면 직접 Copy Constructor를 구현해야 한다.
방법:
- 새로운 메모리 생성
- 기존 값 복사
- 새로운 unique_ptr이 새 메모리를 소유
즉:
Cat A
unique_ptr
↓
int 10
Cat B
unique_ptr
↓
int 10
서로 다른 메모리를 가진 독립 객체가 된다.
9. shared_ptr을 멤버 변수로 사용할 때 주의점
shared_ptr은 복사가 가능하다.
따라서:
class Cat
{
std::shared_ptr<int> value;
};
에서:
Cat a;
Cat b = a;
가능하다.
하지만 내부 동작은:
Cat A
shared_ptr
↓
int
Cat B
shared_ptr
↓
int
처럼 같은 객체를 공유한다.
즉 개발자가 기대한:
Cat A
↓
int A
Cat B
↓
int B
형태가 아니다.
10. shared_ptr 멤버 변수의 해결 방법
클래스 설계자가 의도한 경우라면 문제가 없다.
하지만 사용자 입장에서 혼동될 수 있기 때문에:
방법 1: 문서화
클래스에:
- 내부적으로 shared ownership 사용
- 복사 시 같은 객체 공유
라는 것을 명시한다.
방법 2: clone() 제공
깊은 복사가 필요하면:
Cat clone();
같은 함수를 제공한다.
동작:
원본 객체
↓
새로운 객체 생성
↓
내용 복사
즉 Deep Copy 인터페이스를 제공한다.
11. 실제 사례
대표적인 예:
OpenCV의 cv::Mat
특징:
- 내부 데이터를 공유하는 방식
- 복사 시 실제 데이터 복사가 아닌 참조 증가
- 필요하면 clone()으로 Deep Copy 수행
즉 shared_ptr과 유사한 설계 방식을 사용한다.
핵심 정리
| 스마트 포인터 종류 | 특징 | 사용 목적 |
| unique_ptr | 하나의 소유자만 가능 | 독점 소유, 가장 기본적인 스마트 포인터 |
| shared_ptr | 여러 소유자 가능 | 객체 공유 |
| weak_ptr | 참조만 가능, 카운트 증가 X | 순환 참조 방지 |
반드시 기억할 내용
- weak_ptr은 shared_ptr의 순환 참조 문제를 해결하기 위해 사용한다.
- weak_ptr은 직접 사용할 수 없고 lock()으로 shared_ptr을 얻어야 한다.
- unique_ptr은 복사가 불가능하며 필요하면 직접 Deep Copy를 구현해야 한다.
- shared_ptr 멤버 변수는 복사 시 같은 객체를 공유한다는 점을 반드시 인지해야 한다.
- 스마트 포인터의 동작 원리를 이해하면 메모리 관리와 클래스 설계를 안전하게 할 수 있다.
'Fundamental of CS > : : C++' 카테고리의 다른 글
| [Templates] Introduction (0) | 2024.07.16 |
|---|---|
| [Templates] C++ Templates (0) | 2024.07.16 |
| [Smart Pointer] Shared Pointer (0) | 2024.07.16 |
| [Smart Pointer] Unique Pointer (std::unique_ptr) (0) | 2024.07.16 |
| [Smart Pointer] Smart Pointer (0) | 2024.07.16 |