std::unique_ptr
특징
메서드
이름 |
설명 |
get |
리소스에 대한 포인터를 리턴 |
get_deleter |
delete 함수를 리턴 |
release |
리소스에 대한 포인터를 리턴하고 그 리소스를 해제 |
reset |
리소스를 리셋 |
swap |
리소스를 맞바꿈 |
코드 예제
// 이것은 unique_ptr 예제
#include<iostream>
#include<memory>
using namespace std;
struct MyInt
{
MyInt(int i) :i_(i) {}
~MyInt()
{
cout << "Good bye from " << i_ << endl;
}
int i_;
};
int main()
{
unique_ptr<MyInt> uniquePtr1{ new MyInt(1998) };
cout << "uniquePtr1.get(): " << uniquePtr1.get() << endl;
unique_ptr<MyInt> uniquePtr2{move(uniquePtr1)};
cout << "uniquePtr1.get(): " << uniquePtr1.get() << endl;
cout << "uniquePtr2.get(): " << uniquePtr2.get() << endl;
// uniquePtr1에게 할당된 리소스가 uniquePtr2에게 옮겨졌다.
// 그리고 uniquePtr1의 메모리는 0x00000000이 잡힌다.
{
unique_ptr<MyInt> localPtr{ new MyInt(2003) };
}
//1998이 저장된 클래스는 해제되고 2011로 새롭게 생성된다.
uniquePtr2.reset(new MyInt(2011));
MyInt* myInt = uniquePtr2.release();
cout << "myInt location : " << myInt << endl;
cout << "uniquePtr2.get(): " << uniquePtr2.get() << endl;
//release로 myInt 포인터로 해당 리소스가 리턴됐고 uniquePtr2의 리소스는 해제됐다.
delete myInt;
unique_ptr<MyInt> uniquePtr3{ new MyInt(2017) };
unique_ptr<MyInt> uniquePtr4{ new MyInt(2022) };
cout << "uniquePtr3.get(): " << uniquePtr3.get() << endl;
cout << "uniquePtr4.get(): " << uniquePtr4.get() << endl;
swap(uniquePtr3,uniquePtr4);
cout << "uniquePtr3.get(): " << uniquePtr3.get() << endl;
cout << "uniquePtr4.get(): " << uniquePtr4.get() << endl;
}
// 이것은 unique_ptr의 배열 형태 예시
include <iomanip>
include<iostream>
include<memory>
using namespace std;
class MyStruct
{
public:
MyStruct() :val(count)
{
cout << setw(15) << left << (void*)this << "Hello: " << val << endl;
MyStruct::count++;
}
~MyStruct() {
cout << setw(15) << left << (void*)this << "Good bye:" << val << endl;
MyStruct::count--;
};
private:
int val;
static int count;
};
int MyStruct::count = 0;
int main()
{
// MyStruct 다섯 개로 구성된 myUniqueArray를 생성한다.
{
unique_ptr<MyStruct[]> myUniqueArray{ new MyStruct[5] };
}
//uniqueArray를 생성하고 이 배열의 한 원소에 새로운 MyStruct를 할당한다.
{
unique_ptr<MyStruct[]> myUniqueArray{ new MyStruct[1] };
MyStruct myStruct;
myUniqueArray[0] = myStruct;
}
//myUniqueArray를 생성하고 MyStruct에 새로운 myUniqueArray 원소를 할당
{
unique_ptr<MyStruct[]> myUniqueArray{ new MyStruct[1] };
MyStruct myStruct;
myStruct = myUniqueArray[0];
}
}
std::shared_ptr
특징
- 공유 변수에 대해 레퍼런스 카운터가 있다.
- 레퍼런스 카운터는 자동으로 관리한다. (스코프에 벗어났다던가)
- 레퍼런스 카운터가 0이 되면 리소스를 제거한다.
메서드
이름 |
설명 |
get |
리소스에 대한 포인터를 리턴 |
get_deleter |
delete 함수를 리턴 |
reset |
리소스를 리셋 |
swap |
리소스를 맞바꿈 |
unique |
현재 std::unique_ptr만 리소스를 소유하고 있는지 검사 |
use_count |
레퍼런스 카운트 값을 리턴 |
코드 예제
// 이것은 shared_ptr 예제
#include <iostream>
#include <memory>
using namespace std;
class MyInt
{
public:
MyInt(int v) :val(v)
{
cout << "Hello:" << val << endl;
}
~MyInt()
{
cout << "Good bye" << val << endl;
}
private:
int val;
};
int main()
{
auto sharPtr = make_shared<MyInt>(1998);
cout << "sharedPtr.usecount():" << sharPtr.use_count() << endl; //1
{
shared_ptr<MyInt>locSharPtr(sharPtr);
cout << "locSharPtr.use_count(): " << locSharPtr.use_count() << endl; //2
// 스코프를 벗어났으므로 레퍼런스 카운트가 1 감소
}
cout << "sharPtr,use_count(): " << sharPtr.use_count() << endl; //1
shared_ptr<MyInt> glovSharPtr = sharPtr;
cout << "sharPtr.use_count(): " << sharPtr.use_count() << endl; //2
glovSharPtr.reset();
cout << "sharPtr.use_count(): " << sharPtr.use_count() << endl; //1
sharPtr = shared_ptr<MyInt>(new MyInt(2011)); // hello 2011
// 기존 1998 클래스가 있는 메모리에 새로운 2011 클래스를 생성하더니
// 1998 클래스는 해제되고 레퍼런스 카운트가 1 감소했다.
cout << "sharPtr.use_count(): " << sharPtr.use_count() << endl; //1
//good bye 1998
//good bye 2011
}
만약 자기 자신의 주소를 갖는 std::shared_ptr를 갖고 싶다면
//이것은 shared_ptr_from_this 예제
#include <iostream>
#include <memory>
using namespace std;
class ShareMe :public enable_shared_from_this<ShareMe>
{
public:
shared_ptr<ShareMe> getShared()
{
return shared_from_this();
}
};
int main()
{
//동일한 ShareMe 오브젝트를 공유한다.
shared_ptr<ShareMe> shareMe(new ShareMe);
shared_ptr<ShareMe> shareMe1 = shareMe->getShared();
//두 리소스에 대한 주소가 같다.
cout << "Address of resource of shareMe " << (void*)shareMe.get() << " " << endl;
cout << "Address of resource of shareMe1" << (void*)shareMe1.get() << "" << endl;
cout << "shareMe.use_count():" << shareMe.use_count() << endl;
}
std::weak_ptr
특징
- 레퍼런스 카운터를 수정하지 않는다.
- std::shared_ptr의 순환 구조를 깨는데 도움이 된다.
메서드
이름 |
설명 |
expired |
리소스가 삭제됐는지 검사 |
lock |
리소스에 대한 std:: shared_ptr를 생성 |
reset |
리소스를 리셋 |
swap |
리소스를 맞바꿈 |
use_count |
레퍼런스 카운터의 값을 리턴 |
코드 예제
// 이것은 weak_ptr 예제
#include <iostream>
#include <memory>
using namespace std;
class MyInt
{
public:
MyInt(int i) :i_(i) {}
int get() const { return i_; }
private:
int i_;
};
int main()
{
weak_ptr<MyInt> weakPtr;
auto sharedPtr = make_shared<MyInt>(2011);
cout << "sharedPtr.use_count(): " << sharedPtr.use_count() << endl; //1
//weakPtr 초기화
weakPtr = sharedPtr;
cout << "weakPtr.use_count(): " << weakPtr.use_count() << endl; //1
cout << "weakPtr.expired(): " << weakPtr.expired() << endl;
weak_ptr<MyInt> weakPtr1(sharedPtr);
//리소스를 참조한다.
cout << "sharedPtr ->get()" << sharedPtr->get() << endl;
//weakPtr는 사용하지 않는다.
if (shared_ptr<MyInt> sharedPtr1 = weakPtr.lock())
{
cout << "sharedPtr->get(): " << sharedPtr -> get() << endl;
}
else
{
cout << "Don't get the resource!" << endl;
}
//weakPtr를 리셋한다.
weakPtr.reset();
if (shared_ptr<MyInt> sharedPtr1 = weakPtr.lock())
{
cout << "sharedPtr->get(): " << sharedPtr->get() << endl;// 이건 출력 안됨.
}
else
{
cout << "Don't get the resource!" << endl;
}
// weakPtr2와 weakPtr3를 맞바꾼다.
shared_ptr<MyInt> sharedPtr2(new MyInt(2));
shared_ptr<MyInt> sharedPtr3(new MyInt(3));
weak_ptr<MyInt> weakPtr2(sharedPtr2);
weak_ptr<MyInt> weakPtr3(sharedPtr3);
if (shared_ptr<MyInt> sharedFromWeak2 = weakPtr2.lock())
{
cout << "sharedFromWeak->get(): " << sharedFromWeak2->get() << endl; //2
}
weakPtr2.swap(weakPtr3);
if (shared_ptr<MyInt> sharedFromWeak2 = weakPtr2.lock())
{
cout << "sharedFromWeak2->get(): " << sharedFromWeak2->get() << endl; //3
}
swap(weakPtr2, weakPtr3);
if (shared_ptr<MyInt> sharedFromWeak2 = weakPtr2.lock()) {
cout << "sharedFromWeak2->get(): " << sharedFromWeak2->get() << endl; //2
}
}
순환 참조가 발생하지 않도록 하는 방법
shared_ptr를 사용하다보면 순환 참조가 발생할 수 있다. 쉽게 말하자면 shared_ptr의 리소스가 해제되지 않고 런타임상에 살아있다는 이야기가 되는데, 이런 현상이 발생되면 우리가 스마트 포인터를 사용하는 목적이 사라지게 된다.
이때 이 문제를 해결하기 위해 사용되는 포인터가 weak_ptr이다.
코드 예제
// 이것은 순환 참조에 대한 예제
#include <iostream>
#include <memory>
using namespace std;
struct Son;
struct Daughter;
struct Mother {
~Mother()
{
cout << "Mother gone" << endl;
}
void setSon(const shared_ptr<Son> s)
{
mySon = s;
}
void setDaughter(const shared_ptr<Daughter> d)
{
myDaughter = d;
}
shared_ptr<const Son> mySon;
weak_ptr<const Daughter> myDaughter;
};
struct Son
{
Son(shared_ptr<Mother> m) :myMother(m) {};
~Son()
{
cout << "Son gone" << endl;
}
shared_ptr<const Mother> myMother;
};
struct Daughter
{
Daughter(shared_ptr<Mother> m) :myMother(m) {};
~Daughter()
{
cout << "Daughter gone" << endl;
}
shared_ptr<const Mother> myMother;
};
int main()
{
{
//shared_ptr에 Mother 할당
shared_ptr<Mother> mother = shared_ptr<Mother>(new Mother);
cout << "count: " << mother.use_count() << endl;
// 아들,딸 할당(각각 카운트는 1이다.)
shared_ptr<Son> son = shared_ptr<Son>(new Son(mother));
cout << "Son created, count: " << son.use_count() << endl;
shared_ptr<Daughter> daughter = shared_ptr<Daughter>(new Daughter(mother));
cout << "Daughter created, count: " << son.use_count() << endl;
// mother쪽에서 아들,딸 shared_ptr를 부름
// shared_ptr로 불러진 Son은 카운트 =2
// weak_ptr로 불러진 daughter은 카운트 =1
mother->setSon(son);
cout << "called Son, count: " << son.use_count() << endl;
mother->setDaughter(daughter);
cout << "called daughter, count: " << daughter.use_count() << endl;
//결론: Son은 한번 참조되고 메모리 해제가 안됨 daughter만 됨.
}
}