Smart Pointer
- 일반 포인터를 이용한 동적 할당의 문제점
일반 포인터를 이용한 동적할당은, 프로그래머가 직접 메모리를 추적하고 관리해줘야 하기 때문에, 프로젝트의 규모가 커질 경우 자원 관리에 실패할 가능성이 높아집니다.
스마트 포인터는 일반 포인터의 이런 문제를 해결하기 위해 등장한 솔루션이라고 할 수 있습니다.
- 스마트 포인터란?
스마트 포인터는 동적할당된 메모리 관리를 자동으로 할 수 있도록 디자인된 포인터입니다. 동적할당된 메모리는, RAII 디자인 패턴을 이용하여 객체의 생애주기를 기반으로 메모리를 관리해주며, 객체의 생애주기가 논리적으로 끝났다고 보여질 때, 할당된 메모리를 자동으로 해제해 줍니다.
std::unique_ptr
- 하나의 오너쉽만 허용하는 포인터
댕글링 포인터같은 메모리 문제는, 하나의 인스턴스를 여러 포인터가 참조하고 있을 때(공유하고 있을때) 발생하는 문제입니다. 이를 원천 차단 하기 위해서는 애초에 한 인스턴스를 하나의 포인터만 참조할 수 있도록 디자인 하면 됩니다. unique_ptr은 그런 발상으로 부터 디자인된 스마트 포인터라고 할 수 있습니다.
- 논리적인 생애주기의 끝
현재 인스턴스를 소유하고있는 단 하나의 unique_ptr 변수가 파괴 된다면, 해당 인스턴스를 참조하는 포인터는 더이상 없기 때문에 메모리를 해제하면 됩니다.
- unique_ptr의 제한사항
한개의 오너쉽만 허용하기 때문에, 복사가 불가능합니다. 이동은 당연히 가능합니다.
#include <memory>
int main()
{
{
std::unique_ptr<int> Uptr(new int);
*Uptr = 10;
std::unique_ptr<int> Share = nullptr;
std::unique_ptr<int> Move = nullptr;
Move = std::move(Uptr);
Uptr.reset(new int);
Uptr = std::make_unique<int>(42);
}
std::shared_ptr
- 인스턴스의 공유가 가능한 포인터
앞서 말씀드린 unique_ptr은 안전하지만 너무나 보수적인 방식의 메모리 관리 방법입니다. 포인터의 엄청난 장점 중 하나인 '자원의 공유'를 원천적으로 막아버렸기 때문이죠. std::shared_ptr은 '자원의 공유'를 허용하는 smart pointer입니다. 하나의 인스턴스를 여러 포인터가 소유하고있어도 된다는 뜻입니다.
- 논리적인 생애주기의 끝
RAII 디자인패턴으로 해당 리소스를 관리해야 한다면, shared_ptr이 참조하고 있는 인스턴스는 언제 생애주기가 끝나는가에 대해서 고찰해야 합니다. 간단하게 해당 메모리를 소유하고 있는 포인터 변수가 없다면 해당 인스턴스의 생애주기가 끝났다고 말할 수 있습니다. 이를 지속적으로 추적하기 위해서, 인스턴스를 shared_ptr로 참조할 때 마다 Ref Count를 +1, 참조하고 있는 포인터 변수가 없어질때마다 Ref Count를 -1하며 추적합니다. Ref Count가 0이 되면, 더이상 메모리를 소유하고 있는 포인터가 없다는 뜻이기 때문에, 해당 인스턴스를 delete 합니다.
- std::shared_ptr을 사용할 때의 주의사항
그러면 shared_ptr은 무적이냐? 하면 그건 또 아닙니다. 잘못 사용하게 되면, Ref Count가 0이 되지 않는 저주에 빠질 수 있습니다. 메모리가 해제되어야 할 때 해제 되지 않는다면 Memory Leak이 남을 수 있다는 뜻이죠. 순환참조가 발생한다면 이런 일이 생길 수 있습니다.
#include <memory>
class Node_CirRef
{
public:
Node_CirRef()
{
std::cout << "Node_CirRef Constructor" << std::endl;
}
~Node_CirRef()
{
std::cout << "Node_CirRef Destructor" << std::endl;
}
std::shared_ptr<Node_CirRef> next = nullptr;
};
class Node : public std::enable_shared_from_this<Node>
{
public:
Node()
{
std::cout << "Node Constructor" << std::endl;
}
virtual ~Node()
{
std::cout << "Node Destructor" << std::endl;
}
std::weak_ptr<Node> next;
};
class DerievedNode : public Node
{
public:
DerievedNode()
{
std::cout << "DerievedNode Constructor" << std::endl;
}
~DerievedNode() override
{
std::cout << "DerievedNode Destructor" << std::endl;
}
std::shared_ptr<DerievedNode> ReturnThis()
{
return std::dynamic_pointer_cast<DerievedNode>(this->shared_from_this());
}
DerievedNode* ReturnRawThis()
{
return this;
}
};
int main()
{
{
std::shared_ptr<int> Sptr = std::make_shared<int>(20);
std::shared_ptr<int> Share = nullptr;
Share = Sptr;
std::shared_ptr<int> Move = nullptr;
Move = std::move(Sptr);
Move.reset(new int(10));
const std::shared_ptr<int>& ConstRef = Move;
std::shared_ptr<int>& Ref = Move;
std::shared_ptr<Node> node = std::make_shared<DerievedNode>();
std::shared_ptr<DerievedNode> StaticCast = nullptr;
std::shared_ptr<DerievedNode> DownCast = nullptr;
StaticCast = std::static_pointer_cast<DerievedNode>(node);
DownCast = std::dynamic_pointer_cast<DerievedNode>(node);
{
std::shared_ptr<Node_CirRef> node1 = std::make_shared<Node_CirRef>();
std::shared_ptr<Node_CirRef> node2 = std::make_shared<Node_CirRef>();
node1->next = node2;
node2->next = node1;
}
}
}
std::weak_ptr
- 순환참조를 해결하기 위한 smart pointer
앞서 설명한 std::shared_ptr의 가장 큰 문제점은 순환참조였습니다. 프로젝트를 진행하다 보면 의외로 많이 발생하죠. weak_ptr로 참조하는 인스턴스의 Ref Count는 늘어나지 않습니다(대신 weak Ref Count가 증가합니다). 순환참조는 주로 Ref Count를 늘리면 안되는 참조를 할때 발생하는데, 이때 weak_ptr을 사용한다면 문제를 해결할 수 있습니다.
#include <iostream>
#include <memory>
#include <crtdbg.h>
class Node : public std::enable_shared_from_this<Node>
{
public:
Node()
{
std::cout << "Node Constructor" << std::endl;
}
virtual ~Node()
{
std::cout << "Node Destructor" << std::endl;
}
std::weak_ptr<Node> next;
};
class DerievedNode : public Node
{
public:
DerievedNode()
{
std::cout << "DerievedNode Constructor" << std::endl;
}
~DerievedNode() override
{
std::cout << "DerievedNode Destructor" << std::endl;
}
std::shared_ptr<DerievedNode> ReturnThis()
{
return std::dynamic_pointer_cast<DerievedNode>(this->shared_from_this());
}
DerievedNode* ReturnRawThis()
{
return this;
}
};
int main()
{
{
{
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1;
bool Check = node1->next.expired();
int Count = node1.use_count();
}
}
}