C++프로그래밍/모던C++

shared_ptr 구현해보기

season97 2024. 10. 3. 11:45
728x90
반응형

 개인적인 공부를 위해 포스팅 하는 글입니다.


스마트 포인터 사용 이유

class Knight
{
public:
    Knight() { cout << "나이트생성" << endl; }
    ~Knight() { cout << "나이트소멸" << endl; }


    void Attack()
    {
        if (_target)
        {
            _target->_hp -= _damage;

            cout << "HP:  " << _target->_hp << endl;
        }
    }


public:
    int _hp = 100;
    int _damage = 10;
    Knight* _target = nullptr;
};

int main()
{
    Knight* k1 = new Knight();
    Knight* k2 = new Knight();

    k1->_target = k2;

    delete k2;

    k1->Attack();

    return 0;
    
}

ㆍ k2유저가 접속을 종료해서 삭제해 줬는데 k1이 공격을 했다.... 일반 원시포인터는 사람이 실수할 여지가 있다...

 

★ 솔찍히 요즘 언리얼같은 현대 C++을 사용하면 원시포인터보단 스마트포인터를 많이 쓰게된다.

 


개념 포스팅 링크: https://season97.tistory.com/32

 

쓰레드, 코루틴, 델리게이트와 스마트포인터

▶ 쓰레드란? ㆍ 프로세스 내에서 실행되는 가장 작은 실행 단위 ㆍ 프로세스와 메모리 자원을 공유하며, 여러 쓰레드가 하나의 프로스세스 내에서 동시에 수행될 수 있다.  ▶ 쓰레드의 주요

season97.tistory.com

 

 

▶ 스마트 포인터 ◀

포인터를 알맞는 정책에 따라 관리하는 객체 (포인터를 래핑해서 사용)

shared_ptr

weak_ptr

unique_ptr

언리얼에선 다른 이름으로 래핑되어있다.

 

 

1. shared_ptr

ㆍ 내부적으로 참조 카운팅 방식을 사용하여 몇 개의 shared_ptr이 해당 객체를 참조하고 있는지 추적한다.

ㆍ 참조 카운트는 shared_ptr 이 생성될때나 다른 shared_ptr에 복사될 때 증가한다.

ㆍ 참조 카운트는 shared_ptr이 소멸되거나 다른 객체를 가리킬때 감소한다.

ㆍ 참조 카운트가 0이 되면 (즉 마지막 shared_ptr이 사라지면)  자동으로 해당 객체를 삭제한다

# 템플릿으로 구현해보기

ㆍ 실제 내부적으로는 다르겠지만 shared_ptr처럼 동작하게 구현해봤다.

class RefCountBlock
{
public:
    int _refCount = 1;
};

template<typename T>
class SharedPtr
{
public:
    SharedPtr() {}
    SharedPtr(T* ptr) : _ptr(ptr) 
    {
        if (_ptr != nullptr)
        {
            _block = new RefCountBlock();
            cout << "참조카운트 : " << _block->_refCount << endl;
        }
    }

    SharedPtr(const SharedPtr& sptr) : _ptr(sptr._ptr), _block(sptr._block)
    {
        if (_ptr != nullptr)
        {
            _block->_refCount++;
            cout << "참조카운트 : " << _block->_refCount << endl;
        }
    }

    void operator=(const SharedPtr& sptr) 
    {
        _ptr = sptr._ptr;
        _block = sptr._block;
        if (_ptr != nullptr)
        {
            _block->_refCount++;
            cout << "참조카운트 : " << _block->_refCount << endl;
        }
    }

    ~SharedPtr()
    {
        if (_ptr != nullptr)
        {
            _block->_refCount--;
            cout << "참조카운트 : " << _block->_refCount << endl;

            if (_block->_refCount == 0)
            {
                delete _ptr;
                delete _block;
                cout << "데이터 삭제" << endl;
            }
        }
    }

public:
    T* _ptr = nullptr;
    RefCountBlock* _block = nullptr;
};

int main()
{
    
    SharedPtr<Knight> k1(new Knight());
    SharedPtr<Knight> k2 = k1;
    
    return 0;
    
}

ㆍ 잘 작동한다. 객체가 생성될때 참조카운트가 1이 되고 복사생성자, 복사 대입연산자 호출될때 참조카운트가 증가한다.

스마트 포인터를 사용하면 이제 알아서 메모리를 날려주는 장점이 생긴다.

 

int main()
{

    // SharedPtr<Knight> k1(new Knight());
     //SharedPtr<Knight> k2 = k1;

    shared_ptr<Knight> k1 = make_shared<Knight>();
    {  
        shared_ptr<Knight> k2 = make_shared<Knight>();
        k1->_target = k2; 
    }
    k1->Attack();

    return 0;
}

ㆍ 처음 실행한 코드도 이제는 잘 작동한다. 스코프를 벗어나서 참조카운트가 깎였을 뿐 완전히 소멸된 것은 아니다.

모던cpp부턴 원시포인터 거의 사용 안한다고 보면된다. 

 

 


 

728x90
반응형