포인터
포인터는 C++에서 장점이자 단점이다. 포인터는 직접적으로 메모리를 읽거나 수정할수 있다는 장점이 존재하지만, 그로 인해 발생하는 여러가지 문제점이 너무나도 위험하다는 단점또한 존재한다.
포인터의 문제점: 댕글링포인터(Dangling Pointer)
댕글링 포인터는 포인터의 여러 문제점 중 하나이며, 포인터가 해제된 메모리 영역을 가리키고 있는 상태를 의미한다. 이것의 문제점은 해제된 메모리 영역을 사용했을때 예측이 불가한 동작을 하는데에 있다.
class Mage
{
Mage()
{
}
~Mage()
{
}
public:
void Attack()
{
if(_target)_target->_hp-=damagel
}
public:
int _hp=100;
int damege=10;
Mage* _target=nullptr;
};
int main()
{
Mage* mage1=new Mage();
Mage* mage2=new Mage();
mage1->_target=mage2;
delete mage2;
mage1->Attack();//에러가 나지않고 쓰레기 값이 나오게 된다.
}
위의 코드의 경우 삭제된 메모리를 아무도 사용하지 않았기 때문에 쓰레기 값이 나왔지만 삭제된 메모리를 다른이가 사용하게 된다면 심각한 버그가 일어날수 있다. 이러한 문제점으로 인해 그냥 포인터 사용 보다는 안정성을 높히고자 스마트 포인터의 활용이 높아지고 있다.
스마트포인터(smart pointer)
스마트포인터란, 포인터를 알맞는 정책에 따라 관리하는 객체를 의미한다. 이 스마트 포인터는 세가지로 구분할수 있으며 메모리를 할당할때는 new대신 make_shared를 이용한다.
shared_ptr
스마트포인터의 대표격 포인터로써 레퍼런스 카운터를 관리(몇개가 참조하고 있는지)하는 포인터이다.
class RefCountBlock
{
public:
public:
int _refCount=1;
};
template<typename T>
class Shared_ptr
{
public:
Shared_ptr(){}
Shared_ptr(T* ptr):_ptr(ptr)
{
if(_ptr!=nullptr)
{
_block=new RefCountBlock();
}
}
Shared_ptr(const Shared_ptr& sptr):_ptr(sptr),_block(sptr._block)
{
if(_ptr!=nullptr)//만약 나 이외에 주시하고 있는 객체가 있다면
{
_block->refCount++;
}
}
~Shared_ptr()
{
if(_ptr!=nullptr)
{
_block->_refCount--;
if(_block->_refCount==0)//나 이외에 주시하고 있는 객체가 없다면
{
delete _ptr;
delete _block;
}
}
}
public:
T* _ptr=nullptr;;
RefCountBlock* _refblock=nullptr;
};
위와 같은 개념으로 스마트 포인터는 동작한다. 이런식으로 관리를 하게 되면 포인터처럼 명시적으로 delete를 할 필요가 사라지고 특정조건을 만족하면 자동적으로 delete해주기 때문에 메모리 관리가 보다 수월해지는 장점이 있다.
shared_ptr<Mage>mage1 = make_shared<Mage>();
{
shared_ptr<Mage>mage2 = make_shared<Mage>();
}
mage1->Attack();
원래라면 {}을 벗어난 시점에서 mage2는 delete되어야 하지만 mage1이 주시하고 있기 때문에 delete되지 않고 정상적으로 작동을 하게 되며 댕글링포인터 문제에서 벗어나는 효과도 볼 수 있다.
weak_ptr
weak포인터는 shared포인터의 단점을 보안하기 위해 나온 스마트 포인터이다.
shared_ptr의 단점
shared_ptr<Mage>mage1=make_shared<Mage>();
//mage1[count=1]
{
shared_ptr<Mage>mage2=make_shared<Mage>();
//mage1[count=1]
//mage2[count=1]
mage1->_target=mage2;
mage2->_target=mage1;
//mage1[count=2]
//mage2[count=2]
}
//mage1[count=2]
//mage2[count=1]
mage1->Attack();
위의 코드와 같이 shared포인터의 단점은 서로 주시하게 될 경우 RefCount가 0으로 되지 못하여 포인터를 소멸시키지 못한다는 점에 있다. 소멸을 시키고 싶을 경우 강제적으로 소멸시키고 싶은 포인터를 nullptr로 생명주기르 끊어 주어야한다.
weak_ptr구현
class Mage
{
Mage()
{
}
~Mage()
{
}
public:
void Attack()
{
if(_target.expired()==false)
{
shared_ptr<Mage>sptr=_target.lock();
_target->_hp-=_damage;
}
}
public:
int _hp=100;
int damege=10;
weak_ptr<Mage> _target=nullptr;
};
class RefCountBlock
{
public:
public:
int _refCount=1;
int _weakCount=1;
};
template<typename T>
class Weak_ptr
{
public:
Weak_ptr(){}
Weak_ptr(T* ptr):_ptr(ptr)
{
if(_ptr!=nullptr)
{
_block=new RefCountBlock();
}
}
Weak_ptr(const Weak_ptr& sptr):_ptr(sptr),_block(sptr._block)
{
if(_ptr!=nullptr)//만약 나 이외에 주시하고 있는 객체가 있다면
{
_block->refCount++;
}
}
~Weak_ptr()
{
if(_ptr!=nullptr)
{
_block->_refCount--;
if(_block->_refCount==0)//나 이외에 주시하고 있는 객체가 없다면
{
delete _ptr;
}
}
}
public:
T* _ptr=nullptr;;
RefCountBlock* _refblock=nullptr;
};
위 코드와 같이 shard포인터와의 차이점은 expired로 메모리가 유효하는가를 체크해주고 lock을 이용하여 shared포인터로 변환해주는 점이다. 이렇게 하면 포인터의 생명주기에서 자유로워 진다는 장점이 있지만 명시적으로 체크를 해야하고 결국엔 shared포인터로 다시 변환시켜야하는 단점 또한 존재한다.
unique_ptr
unique포인터는 프로그램 내에서 유일하게 존재해야될때 사용되며 포인터를 공유할수 없는 특징을 갖는다.만약 정말로 공유하고 싶다면 move를 이용해서 넘겨주는 형태로만 공유가 가능하다.
unique_ptr<Mage>uptr=make_unique<Mage>();
unique_ptr<Mage>uptr2=uptr;//Error
unique_ptr<Mage>uptr2=std::move(uptr);
'C++복습' 카테고리의 다른 글
C++복습) C++20/Module (0) | 2022.02.04 |
---|---|
C++복습) C++20/Concept (0) | 2022.01.10 |
C++복습) Modern C++/lambda (0) | 2021.12.30 |
C++복습) Modern C++/전달 참조 (0) | 2021.12.30 |
C++복습) Modern C++/ 오른값 참조( rvalue)와 std::move (0) | 2021.12.29 |
댓글