스마트 포인터는
포인터를 알맞은 규약에 따라 관리하는 객체이며
포인터를 래핑해서 사용한다
스마트 포인터의 종류에는
shared_ptr, weak_ptr, unique_ptr 등이 있다
shared_ptr의 간단 내부 동작원리는 아래와 같다
class Knight
{
public:
Knight() {}
~Knight() {}
void Attack()
{
if (mTarget)
mTarget->mHp -= mDamage;
}
public:
int mHp = 100;
int mDamage = 10;
Knight* mTarget = nullptr;
};
class RefCountBlock
{
public:
int mRefCount = 1;
};
template<typename T>
class SharedPtr
{
public:
SharedPtr() {}
SharedPtr(T* ptr)
: mPtr(ptr)
{
mBlock = new RefCountBlock();
}
SharedPtr(const SharedPtr& sptr)
: mPtr(sptr.mPtr)
, mBlock(sptr.mBlock)
{
if (mPtr != nullptr)
{
mBlock->mRefCount++;
}
}
void operator=(const SharedPtr& sptr)
{
mPtr = sptr.mPtr;
mBlock = sptr.mBlock;
if (mPtr != nullptr)
{
mBlock->mRefCount++;
}
}
~SharedPtr()
{
if (mPtr != nullptr)
{
mBlock->mRefCount--;
if (mBlock->mRefCount == 0)
{
delete mPtr;
delete mBlock;
}
}
}
public:
T* mPtr = nullptr;
RefCountBlock* mBlock = nullptr;
};
int main()
{
SharedPtr<Knight> k2;
{
SharedPtr<Knight> k1(new Knight());
k2 = k1;
}
// k1을 k2가 참조하고 있기 때문에
// k1의 mRefCount가 1이기에 delete 되지 않았음
// k2가 소멸이 될 때, k1도 메모리에서 사라짐
}
shared_ptr의 사용 예시는 아래와 같다
class Knight
{
public:
Knight() {}
~Knight() {}
void Attack()
{
if (mTarget)
mTarget->mHp -= mDamage;
}
public:
int mHp = 100;
int mDamage = 10;
std::shared_ptr<Knight> mTarget = nullptr;
};
int main()
{
std::shared_ptr<Knight> k1 = std::make_shared<Knight>();
{
std::shared_ptr<Knight> k2 = std::make_shared<Knight>();
k1->mTarget = k2;
}// 여기서 k2가 사라져야하지만 shared_ptr로 인해 사라지지 않음
k1->Attack();// 이 부분도 정상 작동
}
하지만, shared_ptr의 문제점은 아래처럼
서로 참조하는 순환 구조의 경우 메모리가 소멸되지 않는다는 것이다
class Knight
{
public:
Knight() {}
~Knight() {}
void Attack()
{
if (mTarget)
mTarget->mHp -= mDamage;
}
public:
int mHp = 100;
int mDamage = 10;
std::shared_ptr<Knight> mTarget = nullptr;
};
int main()
{
std::shared_ptr<Knight> k1 = std::make_shared<Knight>();
std::shared_ptr<Knight> k2 = std::make_shared<Knight>();
k1->mTarget = k2;
k2->mTarget = k1;
// 이처럼 서로 참조하고 있는 순환 구조의 경우에는
// 메모리가 영원히 소멸되지 않는다
// 이 문제를 해결하기 위해서는
k1->mTarget = nullptr;
k2->mTarget = nullptr;
}
위와 같은 shared_ptr의 문제점을 해결하기 위해
등장한 weak_ptr은
shared_ptr의 refCount 말고도 추가적으로
weak_ptr이 해당 객체가 몇 번 참고하는지 관리하는 weakCount를 통해 내부 동작이 이뤄지고
mBlock을 바로 delete 하지 않는다
따라서, shared_ptr 처럼 생명 주기에 직접적으로 관여하지는 않지만
객체가 날라갔는지 날아가지 않았는지를 확인하는 용도로 활용되며
아래처럼 사용이 가능하다
class Knight
{
public:
Knight() {}
~Knight() {}
void Attack()
{
if (mTarget.expired() == false)// 객체의 생존 여부 파악
{
std::shared_ptr<Knight> sptr = mTarget.lock();// shared_ptr로 변환해서 이용
sptr->mHp -= mDamage;
}
}
public:
int mHp = 100;
int mDamage = 10;
std::weak_ptr<Knight> mTarget;
};
int main()
{
std::shared_ptr<Knight> k1 = std::make_shared<Knight>();
std::shared_ptr<Knight> k2 = std::make_shared<Knight>();
k1->mTarget = k2;
k2->mTarget = k1;
// 서로 참조하고 있는 순환 구조이지만
// mTarget은 weak_ptr로 잡혀 있기 때문에
// refCount에 영향을 주지 않는다
k1->Attack();
// mTarget의 expired 여부를 확인하고
// mTarget은 유효하니 shared_ptr로 변환되어 내부가 실행된다
std::shared_ptr<Knight> k3 = std::make_shared<Knight>();
{
std::shared_ptr<Knight> k4 = std::make_shared<Knight>();
k3->mTarget = k4;
k4->mTarget = k3;
}
k3->Attack();
// mTarget의 expired 여부를 확인하고
// mTarget은 유효하지 않으니 실행이 되지 않는다
}
unique_ptr은 단독으로 사용하기 위한 스마트 포인터이고
복사가 막혀있고 이동만 가능한 포인터이며
아래와 같이 사용한다
std::unique_ptr<Knight> uptr = std::make_unique<Knight>();
//std::unique_ptr<Knight> uptr2 = uptr;// 복사 불가능
std::unique_ptr<Knight> uptr2 = std::move(uptr);// 이동 가능
'C++ > [루키스] Modern C++' 카테고리의 다른 글
[MC++] Lambda (0) | 2023.07.02 |
---|---|
[MC++] 전달 참조 (Forwarding reference) (0) | 2023.07.02 |
[MC++] 오른 값 참조 (Rvalue reference)와 std::move (0) | 2023.07.02 |
[MC++] override, final (0) | 2023.07.01 |
[MC++] delete (0) | 2023.07.01 |