C++11에서 새롭게 도입된 스마트 포인터는 unique_ptr, shared_ptr, weak_ptr입니다. unique_ptr은 실제 데이터에 대한 소유권을 오직 하나만 가지도록 하는 것이고 shared_ptr은 실제 데이터에 대한 소유권을 여러 개의 스마트포인터가 공유하면서 참조 카운트 방식으로 관리하여 참조 카운트가 0일때 실제 데이터를 해제하는 것입니다. weak_ptr은 share_ptr처럼 실제 데이터를 공유하지만 참조 카운트의 값을 증가하거나 감소시키지 않는다는 특징이 있습니다. 이 세개의 스마트포인터에 대해 하나씩 살펴 보도록 하겠습니다. 먼저 unique_ptr입니다.
#include "stdafx.h"
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
unique_ptr p1(new int(10));
cout << *p1 << endl;
return 0;
}
위의 코드중 9번은 정수값 10을 관리하는 unique_ptr로써 10번 코드에서처럼 스마트포인트가 관리하는 실제 데이터 값을 화면에 표시하고 있습니다. 중요한 것은 unique_ptr은 실제 데이터에 대한 소유를 오직 하나의 스마트포인트만이 가진다는 것입니다. 이에 대해 확인할 수 있도록 다음 코드를 살펴보겠습니다.
#include "stdafx.h"
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
unique_ptr p1(new int(10));
cout << *p1 << endl;
unique_ptr p2 = p1;
return 0;
}
새롭게 추가된 코드는 오직 12번으로써 기존의 unique_ptr에 대한 스마트 포인터를 또 다른 unique_ptr에 대입함으로써 하나의 실제 데이터를 2개의 unique_ptr이 소유하려고 하는 것입니다. 이에 대해 컴파일 타임에서 12번 코드는 오류가 납니다. 그러나 경우에 따라서는 실제 데이터에 소유를 다른 unique_ptr로 이동시켜야 할 때가 있습니다. 이에 대해 다음 코드를 살펴 보겠습니다.
#include "stdafx.h"
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
unique_ptr p1(new int(10));
cout << *p1 << endl;
unique_ptr p3 = move(p1);
//cout << *p1 << endl;
cout << p1.get() << endl;
cout << p3.get() << endl;
return 0;
}
새로운 코드는 12번입니다. 표준 함수인 move를 이용해 unique_ptr이 가지고 있는 소유권을 다른 unique_ptr이 갖도록 합니다. 이렇게 되면 p1이 관리하는 실제 데이터의 주소는 null이 되어 소유권이 없다는 의미가 되고 원래 p1이 관리하는 실제 데이터의 주소값을 p3가 갖게 됩니다. 결국 주석 처리된 13번 코드가 실행된다면 오류가 발생할 것입니다. 15번과 16번 코드에서 보이는 unique_ptr의 get 함수는 스마트 포인트가 관리하는 실제 데이터의 주소값을 얻는 함수이므로 15번 코드는 null에 해당하는 0을, 16번 코드는 실제 데이터의 주소값을 표시하게 됩니다.
다음은 shared_ptr에 대해 정리해 보겠습니다. shared_ptr은 unique_ptr과 다르게 관리하는 실제 데이터의 소유를 여러개의 shared_ptr이 공유한다는 것입니다. 이렇게 공유를 하다가 실제 데이터에 대해 소유하는 shared_ptr 객체가 존재하지 않을 경우, 즉 참조 개수(Reference Count)가 0개일 경우 실제 데이터를 메모리에서 해제하게 됩니다. 다음 코드를 살펴 보겠습니다.
#include "stdafx.h"
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr p1(new int(10));
shared_ptr p2 = p1;
cout << *p1 << endl;
cout << *p2 << endl;
cout << p2.use_count() << endl;
p1.reset();
cout << p2.use_count() << endl;
p2.reset();
cout << p2.use_count() << endl;
return 0;
}
위의 코드에서 9번 코드에서 생성한 shared_ptr 객체 p1을 같은 타입인 p2에 대대입(assign)함으로써 하나의 실제 데이터에 대해 2개의 shared_ptr 객체가 소유하고 있습니다. 12번과 13번 코드를 통해 이 2개의 shared_ptr 객체가 관리하는 동일한 실제 데이터의 값을 화면에 표시할 것이고요. shared_ptr 객체가 소유하는 실제 데이터에 대한 참조 개수는 use_count 함수를 통해 얻을 수 있고 15번, 17번, 19번 코드를 통해 확인하고 있습니다. 15번 코드는 참조 개수로 2를 출력할 것입니다. shared_ptr의 reset 함수를 통해 실제 데이터에 대한 소유를 더 이상 하지 않겠다고 명시적으로 지정되었으므로 17번 코드는 이제 1을 출력할 것입니다. 그리고 18번 코드에서 다시 한번 reset 함수를 호출했으므로 19번 코드에서 참조 개수로써 이제 0을 표시할 것입니다. 사실 실제 업무단에서는 reset 함수를 직접 해줄 필요가 없습니다. 객체의 scope를 벗어나면 자동으로 이루어지기 때문입니다.
이제 마지막으로 weak_ptr에 대해 정리해 보겠습니다. weak_ptr은 shared_ptr과 참조 카운트를 이용해 실제 데이터에 대한 소유를 함께 한다는 점은 동일하지만 참조 카운트를 증가시키지도 감소시키지도 않는다는 것입니다. 다음 코드를 살펴 보게습니다.
#include "stdafx.h"
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr sp1(new int(10));
weak_ptr wp1 = sp1;
cout << sp1.use_count() << endl;
cout << wp1.use_count() << endl;
return 0;
}
shared_ptr인 sp1을 weak_ptr인 wp1에 할당하고 있고 sp1과 wp1에 대한 참조 카운트를 표시하는 코드입니다. weak_ptr은 참조 카운트의 개수를 증가시키지 않는다고 했으므로 1과 1을 표시할 것입니다. weak_ptr은 lock이라는 함수를 제공하는데 이 함수는 shared_ptr 객체를 생성해 반환해 줍니다. 즉, weak_ptr의 lock 함수가 실행도면 shared_ptr 객체가 생성되므로 자동으로 참조 카운트는 1이 증가될 것입니다. 이에 대한 내용을 아래의 코드를 통해 살펴볼 수 있습니다.
#include "stdafx.h"
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr sp1(new int(10));
weak_ptr wp1 = sp1;
cout << sp1.use_count() << endl;
cout << wp1.use_count() << endl;
{
shared_ptr sp2 = wp1.lock();
cout << sp1.use_count() << endl;
cout << wp1.use_count() << endl;
}
cout << sp1.use_count() << endl;
cout << wp1.use_count() << endl;
return 0;
}
15번 ~ 20번 코드를 살펴보면 새로운 블럭 범위 안에서 weak_ptr 객체인 wp1의 lock 함수를 통해 shared_ptr 객체를 생성하여 sp2에 할당하고 있습니다. 18번과 19번 코드에서 참조 카운트를 출력해 보면 각각 2의 값이 표시됩니다. 즉, 실제 데이터에 대해 3개의 스마트 포인터가 소유하고 있는데 말입니다. 3개가 소유함에도 참조 카운트가 2인 이유는 2개는 shared_ptr이고 1개는 weak_ptr이기 때문입니다. 20번 코드까지 실행되면 자동으로 sp2 객체가 소멸되면서 실제 데이터에 대한 소유도 해제됩니다. 즉 참조 카운트가 1개 감소합니다. 감소했다는 것은 22번과 23번 코드에서 각각 모두 1이 표시되는 것을 통해 확인할 수 있습니다.