스마트 포인터

Jaemyeong Lee·2024년 8월 10일

게임 서버1

목록 보기
105/220

raw 포인터의 한계

문제점

  • new/delete 짝 관리 실패 -> 메모리 누수
  • 해제 후 접근(use-after-free) -> 크래시/메모리 오염
  • 소유권이 불명확해 "누가 delete해야 하는지" 코드 리뷰마다 해석이 갈림

스마트 포인터의 핵심 원리 (RAII)

  • 스마트 포인터는 객체 수명을 스코프 기반으로 자동 관리합니다.
  • "소유권(ownership)"을 타입으로 표현해 설계를 명확히 만듭니다.
타입소유권 모델핵심
unique_ptr단독 소유가장 가볍고 안전한 기본 선택
shared_ptr공유 소유참조 카운트 기반
weak_ptr비소유 참조순환 참조 방지/수명 확인

unique_ptr (기본 권장)

특징

  • 복사 불가, 이동만 가능
  • 소유자가 하나라서 수명 추적이 단순
  • 오버헤드가 작아 기본 소유권 도구로 가장 적합

사용

std::unique_ptr<Knight> k1 = std::make_unique<Knight>();
std::unique_ptr<Knight> k2 = std::move(k1); // 소유권 이전

커스텀 삭제자 (핸들/파일 등)

auto fileCloser = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(fileCloser)> fp(fopen("a.txt", "w"), fileCloser);

shared_ptr (공유 소유)

원리

  • 제어 블록(control block)에 강한 참조 수(strong count)를 저장
  • 복사 시 count 증가, 소멸 시 감소
  • strong count가 0이 되면 관리 대상 객체를 해제

사용

auto k1 = std::make_shared<Knight>();
auto k2 = k1; // 공유 소유

make_shared 권장 이유

  • 객체 + 제어 블록을 한 번에 할당하는 구현이 많아 효율적
  • 예외 안전성과 가독성이 좋음

주의:

  • shared_ptr<T>(raw)를 여러 번 만들면 제어 블록이 분리되어 이중 delete 위험
  • raw 포인터를 임의로 꺼내 장기 보관하지 말 것
  • use_count()는 디버깅 참고용으로만 사용 (동시성 환경에서 로직 의존 금지)

weak_ptr

용도

  • weak_ptr는 strong count를 늘리지 않는 비소유 참조입니다.
  • 객체 접근 시 lock()으로 shared_ptr를 임시 복원해 사용합니다.
  • 순환 참조를 끊을 때 핵심 도구입니다.

사용

std::weak_ptr<Knight> wp = k1;
if (auto sp = wp.lock()) {  // 살아 있을 때만 복원
    sp->Attack();
}

순환 참조 해결 흐름

문제 구조

포인터복사이동RefCount용도
shared_ptr✓ (RefCount++)참조공유 소유
weak_ptr✓ (weak count만)미기여순환 참조 해결
unique_ptr-단일 소유

shared_ptr끼리 서로 강한 참조를 잡으면 count가 0이 되지 않아 누수가 발생합니다.

해결 구조

한쪽 연결을 weak_ptr로 바꿔 사이클을 끊습니다.

[문제] K1 ──shared_ptr──► K2
        ▲                 │
        │    shared_ptr   │
        └─────────────────┘
        → RefCount 영원히 2, 메모리 누수

[해결] K1 ──weak_ptr────► K2
        ▲                 │
        │    shared_ptr   │
        └─────────────────┘
        → K1 소멸 시 K2 RefCount 0, 정상 해제

enable_shared_from_this (고급, 중요)

this로부터 안전한 shared_ptr를 얻고 싶다면 enable_shared_from_this를 사용합니다.

class Knight : public std::enable_shared_from_this<Knight> {
public:
    std::shared_ptr<Knight> GetSelf() { return shared_from_this(); }
};

주의:

  • 객체가 처음부터 shared_ptr로 관리되지 않으면 shared_from_this()는 예외/오류
  • std::shared_ptr<Knight>(this)를 수동으로 만들면 제어 블록 분리로 매우 위험

선택 가이드 + 체크 질문

어떤 포인터를 쓸까?

상황권장
소유자가 하나unique_ptr
여러 시스템이 생명주기를 공유shared_ptr
관찰만 하고 소유권은 없음weak_ptr

체크 질문 (스스로 답해보기)

  • unique_ptr를 기본 선택으로 권장할까?
  • 순환 참조가 생기면 왜 객체가 해제되지 않을까?
  • shared_ptr(this)가 위험한 이유를 제어 블록 관점에서 설명할 수 있는가?

profile
李家네_공부방

0개의 댓글