Outdated/Library

[Modern C++ 14] Smart Pointer - std::weak_ptr

해달 2018. 1. 4. 18:04

Modern C++ 14

주제 : Smart Pointer(std::unique_ptr, std::shared_ptr, std::weak_ptr)
헤더 : <memory>
설명
모던 C++에는 스마트 포인터 객체가 추가되었다. 기존 C 스타일의 포인터는 동적으로 생성된 객체들을 프로그래머가 잘 제어했어야 했다. 메모리가 잘 관리되지 않는다면 메모리 누수(memory leak), 댕글링 포인터(dangling pointer), 중복 삭제(double free) 등의 문제가 생길 수 있다. 스마트 포인터는 RAII(Resource Acquisition Is Initialization) 기법을 이용해 메모리 해제를 자동화시킴으로써 프로그래머의 실수를 줄여 보다 더 안전한 프로그래밍을 할 수 있게 되었다. 또한, 템플릿으로 작성되어, 어떤 타입이든 사용 가능하다. 스마트 포인터는 세 가지로 나뉘는 데, 이번 게시물에는 std::weak_ptr에 대해서 알아보도록 하겠다.

- std::weak_ptr

std::weak_ptr std::shared_ptr처럼 참조 횟수로 메모리를 관리하는 포인터이다. 두 개의 차이점은 std::shared_ptr은 '소유'한다는 의미가 있는 데, std::weak_ptr은 소유권을 주장하진 않고, 단순히 현재 객체가 사용되고 있는지, 아닌지에 대해서만 판별하기 위해 사용된다. 즉, std::shared_ptr를 보조하기 위해 쓰이는 것이다. 좀 더 구체적으로 얘기하자면 순환 참조 고리 를 끊는 데에 사용 된다. 사용 방법은 다음과 같다.

#include <memory>
#include <iostream>
using namespace std;

int main()
{
    // 참조 횟수 1 증가.
    shared_ptr<int> sp = std::make_shared<int>(10);
    // 초기화 방법
    // weak 참조 횟수 1 증가. 
    weak_ptr<int> wp(sp);
    {
        // weak 참조 횟수 1 증가. 현재 2
        // 참조 횟수는 변동 없음
        weak_ptr<int> wp2 = wp;
        //weak_ptr<int> wp2(sp);
        //weak_ptr<int> wp2(wp);
        cout << "현재 참조 횟수 : " << sp.use_count() << endl;
        cout << "현재 weak 참조 횟수 : " << wp.use_count() << endl;
    }
    // weak 참조 횟수 1 감소. 현재 1
    cout << "현재 weak 참조 횟수 : " << wp.use_count() << endl;
    if (shared_ptr<int> sp1 = wp.lock())
    {
        cout << *sp1 << endl;
    }
    else
    {
        cout << "자원 획득 실패\n";
    }

    // 참조 횟수 0. 자원 삭제
    sp.reset();
    if (wp.expired())
    {
        cout << "자원이 삭제됨\n";
    }

    return 0;
    // weak 참조 횟수 0. 매니저 객체 삭제
}

weak 참조 횟수라는 것을 얘기했는 데, 지난 std::shared_ptr에 대한 게시물에 이런 그림이 있었다.

MSDN std::shared_ptr

사진 출처 : MSDN

실제로는 저 블록 참조에 std::shared_ptr이 가리키는 횟수와 std::weak_ptr이 가리키는 횟수로 나뉘어져 있다. VS2015 디버그 모드에서 다음과 같이 확인할 수 있다.

weak_ptr

strong_ref가 std::shared_ptr이 가리키는 횟수이고, weak_ref가 std::weak_ptr이 가리키는 횟수이다. 블록 참조 객체는 strong_ref와 weak_ref 모두 0이어야 삭제되는 것이다.

주로 사용되는 메소드는 다음과 같다.

이름설명
expired자원이 삭제되었는지 점검한다.
use_count참조 횟수를 반환한다.
reset자원을 재설정 한다.
swap인자로 전달된 포인터와 자원을 치환한다.
lock자원에 대한 std::shared_ptr 객체를 생성한다.

Terminology
순환 참조 고리
순환 참조 고리란, `std::shared_ptr`이 가리키는 객체가 서로서로 가리키게 되어 메모리가 해제되지 않는 현상을 말한다. 그림으로 보면 다음과 같다.다음처럼 되어 있다고 할 때, 저 객체들을 가리키는 sp1, sp2, sp3가 소멸되었다고 하자. 그럼 우리는 각 객체들이 삭제되기를 기대할 것이다. 그럼 다음과 같아지는 데, 객체 내부에 있는 `std::shared_ptr`로 인해 참조 횟수가 남아 있으므로 기대한 것과 달리 객체가 삭제되지 않는다. 이를 끊기 위해 `std::weak_ptr`을 사용한다.이런 식으로 사용해주면 객체에 대한 강한 참조 횟수가 0이므로 객체는 삭제되게 된다. 단, 블록 참조 객체는 삭제되지 않는다.