전체 코드

🚀 1. 스마트 포인터란?

스마트 포인터(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 호출 필요 없음.
  • 예외 안전성 → 예외 발생 시에도 객체가 정상적으로 해제됨.
  • 포인터 오류 방지 → 잘못된 메모리 접근을 방지.

📂 2. 코드 분석


🎯 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을 사용한 순환 참조 방지

  • _targetshared_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;
}

순환 참조 문제 발생

  • k1k2가 서로를 shared_ptr로 참조하여 순환 참조 문제 발생.
  • 해결책: _targetweak_ptr로 변경하여 참조 카운트 증가를 방지.

unique_ptr 활용

  • unique_ptr<Knight> uptr = make_unique<Knight>();
  • 소유권 이전: std::move(uptr);을 사용하여 unique_ptr 이동 가능.

📌 3. 주요 개념 정리

🎯 스마트 포인터 요약

스마트 포인터특징장점단점
shared_ptr참조 카운트를 이용한 공유메모리 자동 관리순환 참조 문제 발생 가능
weak_ptrshared_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로 이동만 가능).
  • 소유권을 단독으로 유지하여 메모리 누수 방지.

profile
李家네_공부방

0개의 댓글