C++ 에서는 스마트 포인터라는 것을 사용하여 포인터를 더욱 안전하고 효율적으로 사용할 수 있습니다.
스마트 포인터에는 다음과 같이 3가지가 존재합니다.
- unique_ptr
- shared_ptr
- weak_ptr
기존의 포인터의 경우에는 new
와 delete
가 한 쌍으로 사용되었으나 프로그래머의 실수로 delete
를 하지 않게 될 경우에는 메모리 누수로 이어졌습니다.
오늘은 이 스마트 포인터 중 shared_ptr
을 소개 합니다.
공용 소유가 가능한 포인터 (shared_ptr) (C++11)
단 하나의 소유권을 가지는 unique_ptr
과는 달리 shared_ptr
은 공용 소유가 가능한 포인터로 한 객체를 여러개의 shared_ptr
로 공용 소유가 가능합니다.
template<class T> class shared_ptr;
이렇게 공용 소유가 가능하게 되어
unique_ptr
에서는 위와 같이 불가능 했던 것들이
이렇게 가능하게 되었습니다.
shared_ptr의 소멸
shared_ptr
은 다중 소유가 가능하기 때문에 참조하고 있는 포인터들의 개수를 가지고 있습니다.
바로 use_count()
함수를 이용하면 개수를 구할 수 있습니다.
long use_count() const noexcept;
use_count()
함수를 사용해보면
#include <iostream>
#include <memory>
#include <string>
class Human
{
private:
int m_nAge;
std::string m_strName;
public:
Human(int nAge = 0, std::string strName = "") : m_nAge(nAge), m_strName(strName)
{ printf(" Human 생성 (%d, %s)\n", nAge, strName.c_str()); }
~Human() { printf(" Human 소멸 (%d, %s)\n", m_nAge, m_strName.c_str()); }
void Print() { printf(" Age : %d, Name : %s\n", m_nAge, m_strName.c_str()); }
};
int main(void)
{
std::shared_ptr<Human> pHuman1(new Human(10, "Kim"));
std::shared_ptr<Human> pHuman2(pHuman1);
std::shared_ptr<Human> pHuman3 = pHuman1;
printf("pHuman1 Count : %d\n", pHuman1.use_count());
printf("pHuman2 Count : %d\n", pHuman2.use_count());
printf("pHuman3 Count : %d\n", pHuman3.use_count());
return 0;
}
shared_ptr
은 이 참조 개수가 0이 되거나 개별적으로 초기화(reset()
함수)를 해주는 경우 소멸 됩니다.
참조 되는 범위는 unique_ptr
과 동일하게 해당하는 scope
내 입니다.
#include <iostream>
#include <memory>
#include <string>
class Human
{
private:
int m_nAge;
std::string m_strName;
public:
Human(int nAge = 0, std::string strName = "") : m_nAge(nAge), m_strName(strName)
{ printf(" Human 생성 (%d, %s)\n", nAge, strName.c_str()); }
~Human() { printf(" Human 소멸 (%d, %s)\n", m_nAge, m_strName.c_str()); }
void Print() { printf(" Age : %d, Name : %s\n", m_nAge, m_strName.c_str()); }
};
int main(void)
{
std::shared_ptr<Human> pHuman1(new Human(10, "Kim"));
printf("pHuman1 Count : %d\n", pHuman1.use_count());
{
std::shared_ptr<Human> pHuman2(pHuman1);
printf(" pHuman1 Count : %d\n", pHuman2.use_count());
printf(" pHuman2 Count : %d\n", pHuman2.use_count());
{
std::shared_ptr<Human> pHuman3(pHuman2);
printf(" pHuman1 Count : %d\n", pHuman2.use_count());
printf(" pHuman2 Count : %d\n", pHuman2.use_count());
printf(" pHuman3 Count : %d\n", pHuman3.use_count());
}
printf(" pHuman1 Count : %d\n", pHuman2.use_count());
printf(" pHuman2 Count : %d\n", pHuman2.use_count());
}
printf("pHuman1 Count : %d\n", pHuman1.use_count());
return 0;
}
reset()
함수를 이용해서 직접 참조를 변경하거나 초기화할 수 있습니다.
// 관리하고 있는 객체의 소유권을 해제 (Releases the ownership of the managed object, if any.)
// reset()은 shared_ptr().swap(*this);와 같은 동작을 수행합니다.
void reset() noexcept;
// 관리하고 있는 객체의 소유권을 해제하고 새로운 객체(Y)의 소유권을 갖습니다.
template<class Y>
void reset(Y* ptr);
// 관리하고 있는 객체의 소유권을 해제하고 새로운 객체(Y)의 소유권을 갖습니다.
// 또한 사용자 Deleter(직접 선언한 제거 함수)를 설정합니다.
template<class Y, class Deleter>
void reset(Y* ptr, Deleter d);
// 관리하고 있는 객체의 소유권을 해제하고 새로운 객체(Y)의 소유권을 갖습니다.
// 또한 사용자 Deleter(직접 선언한 제거 함수)를 설정합니다.
// 또한 사용자 할당자(직접 선언한 내부 할당에 사용할 함수)를 설정합니다.
template<class Y, class Deleter, class Alloc>
void reset(Y* ptr, Deleter d, Alloc alloc);
여기서 주로 Deleter
의 경우에는 배열 포인터같은 객체의 소유권을 가질 때 해당 소유권을 제거할 때 사용합니다.
(일반적으로 Deleter
를 따로 선언하지 않을 경우에는 소유권을 제거할 때 delete Y;
(Y는 객체)를 호출합니다.)
#include <iostream>
#include <memory>
#include <string>
class Human
{
private:
int m_nAge;
std::string m_strName;
public:
Human(int nAge = 0, std::string strName = "") : m_nAge(nAge), m_strName(strName)
{ printf(" Human 생성 (%d, %s)\n", nAge, strName.c_str()); }
~Human() { printf(" Human 소멸 (%d, %s)\n", m_nAge, m_strName.c_str()); }
void Print() { printf(" Age : %d, Name : %s\n", m_nAge, m_strName.c_str()); }
};
template <class T>
void ArrDelete(T* arr)
{
if (arr != nullptr)
{
delete[] arr;
arr = nullptr;
printf("Delete Array.\n");
}
}
int main(void)
{
std::shared_ptr<Human> pHuman1(new Human(10, "Kim"));
printf("H1 H2\n");
printf("%02d\n", pHuman1.use_count());
std::shared_ptr<Human> pHuman2(pHuman1);
printf("%02d %02d\n", pHuman1.use_count(), pHuman2.use_count());
pHuman1.reset(); // pHuman1 초기화
printf("%02d %02d\n", pHuman1.use_count(), pHuman2.use_count());
pHuman1.reset(new Human(44, "Oh")); // pHuman1 초기화 겸 새로운 객체 소유
printf("%02d %02d\n", pHuman1.use_count(), pHuman2.use_count());
Human* arrHum = new Human[10];
std::shared_ptr<Human> pHuman3;
pHuman3.reset(arrHum, ArrDelete<Human>); // pHuman3 초기화 겸 새로운 객체 및 Deleter 선언
return 0;
}
참조가 되는 경우 (Call by Reference, Call by Value)
Call by Reference
인 경우에는 참조의 개수가 늘어나지 않습니다.
하지만 Call by Value
의 경우에는 참조의 개수가 늘어납니다.
#include <iostream>
#include <memory>
#include <string>
class Human
{
private:
int m_nAge;
std::string m_strName;
public:
Human(int nAge = 0, std::string strName = "") : m_nAge(nAge), m_strName(strName)
{ printf(" Human 생성 (%d, %s)\n", nAge, strName.c_str()); }
~Human() { printf(" Human 소멸 (%d, %s)\n", m_nAge, m_strName.c_str()); }
void Print() { printf(" Age : %d, Name : %s\n", m_nAge, m_strName.c_str()); }
};
// Call by Reference
void PrintHumanCR(const std::shared_ptr<Human>& human)
{
printf(" PrintHumanCR : Human Count : %d", human.use_count());
human->Print();
}
// Call by Value
void PrintHumanCV(const std::shared_ptr<Human> human)
{
printf(" PrintHumanCV : Human Count : %d", human.use_count());
human->Print();
}
int main(void)
{
std::shared_ptr<Human> pHuman1(new Human(10, "Kim"));
printf("pHuman1 Count : %d\n", pHuman1.use_count());
PrintHumanCR(pHuman1);
printf("pHuman1 Count : %d\n", pHuman1.use_count());
PrintHumanCV(pHuman1);
printf("pHuman1 Count : %d\n", pHuman1.use_count());
return 0;
}
소유한 객체가 없으면 null
shared_ptr
이 소유한 객체가 없다면 null
값을 반환합니다.
int main(void)
{
std::shared_ptr<Human> pHuman1;
if (pHuman1 == nullptr)
{
printf("pHuman1 is null\n");
}
if (pHuman1 == NULL)
{
printf("pHuman1 is null\n");
}
if (!pHuman1)
{
printf("pHuman1 is null\n");
}
return 0;
}
한 객체를 둘 이상의 shared_ptr이 소유하게 되면 생기는 문제
Deleter
가 호출된 이후에 문제가 생깁니다.
int main(void)
{
Human* pHuman = new Human(55, "Hi");
std::shared_ptr<Human> pHuman1(pHuman);
std::shared_ptr<Human> pHuman2(pHuman);
printf("pHuman1 - ");
pHuman1->Print();
printf("pHuman2 - ");
pHuman2->Print();
pHuman2.reset(); // <-- 여기서 pHuman은 제거됨
printf("pHuman1 - ");
pHuman1->Print(); // <-- pHuman이 제거되었으므로 에러
printf("pHuman2 - ");
pHuman2->Print();
return 0;
}
객체가 사라지게 됨으로 에러가 발생하여 함수가 종료되어버립니다.
서로를 참조하여 생긴 문제
shared_ptr
의 장점은 참조 개수가 0이 되면 사라진다는 것과 단점은 참조 개수가 0이 되면 사라진다는 것입니다.
이게 무슨 말이냐면 간단하게 shared_ptr
끼리 서로 참조하게 되면 참조 개수가 0이 되질 못해 Leak이 발생합니다.
#include <iostream>
#include <memory>
#include <string>
class Human
{
private:
int m_nAge;
std::string m_strName;
std::shared_ptr<Human> m_ptr;
public:
Human(int nAge = 0, std::string strName = "") : m_nAge(nAge), m_strName(strName)
{ printf("Human 생성 (%d, %s)\n", nAge, strName.c_str()); }
~Human() { printf("Human 소멸 (%d, %s)\n", m_nAge, m_strName.c_str()); }
// shared_ptr를 교체하는 함수
void SetPtr(std::shared_ptr<Human> ptr) { m_ptr = ptr; }
};
int main(void)
{
std::shared_ptr<Human> pHuman1(std::make_shared<Human>(22, "Hong"));
std::shared_ptr<Human> pHuman2(std::make_shared<Human>(33, "Kim"));
pHuman1->SetPtr(pHuman2);
pHuman2->SetPtr(pHuman1);
return 0;
}
이렇게 소멸자가 호출되지 않습니다.
이렇게 shared_ptr
을 사용할 때는 서로 참조하는 경우를 주의하며 사용해야 합니다.
shared_ptr을 안전하게 생성하기 (std::make_shared())
shared_ptr
을 생성할 때 표준 라이브러리 함수인 std::make_shared()
로 생성하도록 권장하고 있습니다.
template <class T, class... Args>
shared_ptr<T> make_shared(Args&&... args);
#include <iostream>
#include <memory>
#include <string>
class Human
{
private:
int m_nAge;
std::string m_strName;
public:
Human(int nAge = 0, std::string strName = "") : m_nAge(nAge), m_strName(strName)
{
printf(" Human 생성 (%d, %s)\n", nAge, strName.c_str());
}
~Human() { printf(" Human 소멸 (%d, %s)\n", m_nAge, m_strName.c_str()); }
void Print() { printf(" Age : %d, Name : %s\n", m_nAge, m_strName.c_str()); }
};
int main(void)
{
std::shared_ptr<Human> pHuman1(std::make_shared<Human>(22, "Hong"));
std::shared_ptr<Human> pHuman2(pHuman1);
std::shared_ptr<Human> pHuman3 = pHuman1;
printf("pHuman1 Count : %d\n", pHuman1.use_count());
printf("pHuman2 Count : %d\n", pHuman2.use_count());
printf("pHuman3 Count : %d\n", pHuman3.use_count());
return 0;
}
참고 자료
'Study > C++' 카테고리의 다른 글
[C++] 자료구조 Queue(큐) 만들어 보기 (0) | 2022.09.22 |
---|---|
[C++11] 스마트 포인터3 (weak_ptr) (0) | 2022.09.21 |
[C++11] 스마트 포인터1 (unique_ptr) (0) | 2022.09.19 |
[C++] std::array (C++ 표준 라이브러리 배열 컨테이너 이야기) (0) | 2022.09.18 |
[C++] Google 공룡 게임 만들어보기 (2) | 2022.02.05 |