[C++] 스마트 포인터 - unique_ptr, shared_ptr, weak_ptr

    스마트 포인터란

    포인터처럼 사용하는 클래스 템플릿

    사용이 끝난 메모리를 자동으로 해제해 메모리 누수를 방지한다.

     

    스마트 포인터 유형

    • unique_ptr
    • shared_ptr
    • weak_ptr

    unique_ptr

    하나의 스마트 포인터만이 특정 객체를 소유할 수 있는 스마트 포인터

     

    해당 객체의 소유권을 가지고 있을 때만 소멸자가 해당 객체를 삭제할 수 있다.

    unique_ptr 인스턴스는 move() 함수를 통해 소유권 이전이 가능하지만 복사할 수는 없다.

    소유권이 이전되면 이전 unique_ptr 인스턴스는 해당 객체를 소유하지 않게 재설정된다.

    // 문법
    unique_ptr<객체> 변수명(new 객체);
    unique_ptr<객체> 변수명 = make_unique<객체>(인수);

     

    #include <iostream>
    #include <memory>
    
    usingnamespace std;
    
    int main() 
    {
    	unique_ptr<int> ptr_1(new int(10));	// int형 unique_ptr 선언과 동시에 초기화
    	unique_ptr<int> ptr_2 = move(ptr_1);	// ptr_1에서 ptr_2로 소유권 이전
    
    	unique_ptr<int> ptr_3 = ptr_2;	// ERROR! 대입을 이용한 복사는 에러를 발생
    
    	ptr_1.reset();	// ptr_1이 가리키고 있는 메모리 영역 삭제
        
        return 0;
    }

    영역을 벗어나면 개체가 자동으로 삭제되니 delete를 통해 메모리를 해제할 필요가 없다.


    shared_ptr

    하나의 특정 객체를 참조하는 스마트 포인터가 몇 개인지를 참조하는 스마트 포인터

     

    특정 객체에 새로운 shared_ptr가 생길 때마다 참조 개수(Reference count)가 증가하며 소멸될 때마다 감소한다.

    마지막 shared_ptr의 수명이 다해 참조 횟수가 0이 되면 delete 키워드를 사용해 자동으로 메모리를 해제한다.

    // 문법
    
    shared_ptr<객체> 변수명(new 객체);
    shared_ptr<객체> 변수명 = make_shared<객체>(인수);

     

    #include <iostream>
    #include <memory>
    
    usingnamespace std;
    
    class Person
    {
    private:
    	string name_;
    	int age_;
    public:
    	Person(const string& name, int age)
        : name_(name), age_(age)
        { cout << "생성자 호출" << endl; }
        
        ~Person() { cout << "소멸자 호출" << endl; }
    }  
    
    int main()
    {
    	shared_ptr<Person> shin = make_shared<Person>("짱구", 5);
    	cout << "소유자 수: " << shin.use_count() << endl;
        
    	shared_ptr<Person> kim = shin;
    	cout << "소유자 수: " << shin.use_count() << endl;
        
    	kim.reset();
    	cout << "소유자 수: " << shin.use_count() << endl;
    }

    실행 결과

    생성자 호출
    소유자 수: 1
    소유자 수: 2
    소유자 수: 1
    소멸자 호출

     

    순환 참조

    서로 상대방을 가리키는 shared_ptr가 있을 경우 참조 개수가 0이 되지 않아 메모리가 해제되지 않는 경우가 있다.

    #include <iostream>
    #include <memory>
    
    class Person
    {
    private:
    	string name_;
    	int age_;
    	shared_ptr<Person> mate_;
        
    public:
    	Person(const string& name, int age)
    	: name_(name), age_(age)
    	{ cout << "생성자 호출" << endl; }
        
    	~Person() { cout << "소멸자 호출" << endl; }
        
    	void set_mate(shared_ptr<Person> m) { mate_ = m; }
    }
    
    int main()
    {
    	shared_ptr<Person> shin = make_shared<Person>("짱구", 5);
    	shared_ptr<Person> kim = make_shared<Person>("철수", 5);
        
    	shin->set_mate(kim);
    	kim->set_mate(shin);
    }

    실행 결과

    생성자 호출
    생성자 호출

    객체 shin이 해제되려면 shin을 가리키고 있는 shared_ptr의 참조 개수가 0이 되어야 한다.

    즉 shin을 가리키는 객체 kim이 해제되어야 하는데 kim 또한 이를 가리키고 있는 객체 shin이 해제되어야 한다.

    그렇기 때문에 실행 결과처럼 소멸자가 호출되지 않는다.

     

    이를 해결하기 위해 나온 것이 weak_ptr다.


    weak_ptr

    shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공하지만 참조 개수에 포함되지 않는 스마트 포인터

    shared_ptr의 객체를 참조할 때 참조 개수를 올리진 않지만 shared_ptr의 weak reference count는 증가시킨다. 이는 객체 소멸에 전혀 관여하지 않는다.

     

    weak_ptr는 shared_ptr로부터의 복사 생성, 대입 연산으로만 할당이 가능하다.

    // 문법
    weak_ptr<객체> 변수명(share_ptr 포인터)
    weak_ptr<객체> 변수명 = shared_ptr 포인터
    
    
    // 예시
    shared_ptr<int> s_ptr = make_shared<int>(10);
    
    weak_ptr<int> w1_ptr(s_ptr);
    weak_ptr<int> w2_ptr = s_ptr;

     

    weak_ptr는 할당받은 자원을 사용하려면 lock() 함수를 통해 shared_ptr 유형으로 convert 해야 한다.

    할당받은 자원의 shared_ptr의 참조 개수가 0이 되어 해제되면 expire 돼서 사용할 수 없게 된다.

    expire 된 상태에 lock() 함수를 호출하면 empty 상태의 shared_ptr가 반환된다.

    #include <iostream>
    #include <memory>
    
    usingnamespace std;
    
    class Person
    {
    private:
    	string name_;
    	int age_;
    	shared_ptr<Person> mate_;
        
    public:
    	Person(const string& name, int age)
    	: name_(name), age_(age)
    	{ cout << "생성자 호출" << endl; }
        
    	~Person() { cout << "소멸자 호출" << endl; }
        
    	void set_mate(shared_ptr<Person> m) { mate_ = m; }
    }  
    
    int main()
    {
    	shared_ptr<Person> shin = make_shared<Person>("짱구", 5);
    	shared_ptr<Person> kim = make_shared<Person>("철수", 5);
        
    	weak_ptr<Person> w1_shin(shin);	// 복사 생성자로 객체 생성
    	weak_ptr<Person> w2_shin = wc_shin;	// 대입 연산자로 객체 생성
        
    	w1_shin->set_mate(kim); // ERROR! weak_ptr는 직접적으로 자원에 접근할 수 없음
        
    	if (w1_shin.expired() == false) // shared_ptr의 참조 개수가 0이 아닌지 확인
    	{
    		shared_ptr<Person> c_shin = w1_shin.lock();	// shared_ptr로 변환
    		c_shin->set_mate(kim);
    	} // c_shin은 이 영역을 벗어나면서 해제됨
    }

     

    댓글