본문으로 바로가기

weak_ptr

메모리 누수를 방지하기 위해서 우리는 스마트 포인터를 사용하려 한다. 하지만 클래스의 멤버 변수에서 shared_ptr을 사용할 경우 순환 참조가 발생할 경우 메모리 누수가 발생한다. 이를 방지하기 위해서 weak_ptr을 사용할 수 있다.

 

weak_ptr을 사용하면 shared_ptr처럼 오브젝트를 가리킬 수 있지만 Reference count에는 아무런 영향을 주지 않는다. 아래의 예시를 살펴보자. weak_ptr이 객체를 가리키고 있지만 shared_ptr이 해제될 때 함께 해제되는 모습을 확인할 수 있다. 또한 weak_ptr이 객체를 참조하고 있지만 참조 카운터는 증가하지 않은 모습도 확인할 수 있다.

class A {
public:
    A(int n) : num(n) { std::cout << "A() | num: " << num  << std::endl; }
    int GetNum() const { return num; }
    ~A() { std::cout << "~A() | num: " << num << std::endl; }
private:
    int num;
};

int main() 
{    
    std::weak_ptr<A> wPtr;

    std::cout << "Begin scope" << std::endl;
    {
        std::shared_ptr<A> sPtr = std::make_shared<A>(10);
        wPtr = sPtr;
        std::cout << "count: "<< sPtr.use_count() << std::endl;
    }
    std::cout << "End scope" << std::endl;

    return 0;
}

출력 결과

Begin scope
A() | num: 10
count: 1
~A() | num: 10
End scope

weak_ptr을 shared_ptr로 전환

weak_ptr을 사용하기 위해서는 shared_ptr로 전환을 한 후 사용해야 한다. 이때 멤버함수 lock을 사용하면 전환 성공시 shared_ptr을 반환하고, 전환 실패 시 nullptr을 반환한다. 아래의 예시에서 shared_ptr을 반환할 경우 참조 카운터가 증가함을 확인할 수 있다.

class A {
public:
    A(int n) : num(n) { std::cout << "A() | num: " << num  << std::endl; }
    int GetNum() const { return num; }
    ~A() { std::cout << "~A() | num: " << num << std::endl; }
private:
    int num;
};

int main() 
{    
    std::weak_ptr<A> wPtr;

    std::cout << "Begin scope" << std::endl;
    {
        std::shared_ptr<A> sPtr = std::make_shared<A>(10);
        wPtr = sPtr;
        std::cout << "count: "<< sPtr.use_count() << std::endl;

        if (const auto spt = wPtr.lock())
        {
            std::cout << "Successful creation of shared_ptr!" << std::endl;
            std::cout << "count: " << sPtr.use_count() << std::endl;
        }
        else
        {
            std::cout << "Failed to create shared_ptr" << std::endl;
            std::cout << "count: " << sPtr.use_count() << std::endl;
        }
    }
    std::cout << "End scope" << std::endl;

    if (const auto spt = wPtr.lock())
    {
        std::cout << "Successful creation of shared_ptr!" << std::endl;
    }
    else
    {
        std::cout << "Failed to create shared_ptr" << std::endl;
    }

    return 0;
}

출력 결과

Begin scope
A() | num: 10
count: 1
Successful creation of shared_ptr!
count: 2
~A() | num: 10
End scope
Failed to create shared_ptr

유효성 검사

weak_ptr에서 lock 멤버함수를 사용하면 반환 값으로 성공 시 shared_ptr을 실패 시 nulltpr을 반환한다. weak_ptr에서 객체에 접근하기 위해서는 이러한 유효성 검사를 거쳐야 한다. 다른 방법으로는 expired 멤버함수를 활용할 수 있다. expired 멤버함수는 객체가 만료되었다면 true를 반환하고 객체가 만료되지 접근 가능하다면 false를 반환한다.

int main() 
{    
    std::weak_ptr<A> wPtr;

    std::cout << "Begin scope" << std::endl;
    {
        std::shared_ptr<A> sPtr = std::make_shared<A>(10);
        wPtr = sPtr;
        std::cout << "count: "<< sPtr.use_count() << std::endl;

        if (wPtr.expired())
        {
            std::cout << "wPtr isn't valid" << std::endl;
        }
        else
        {
            std::cout << "wPtr is valid" << std::endl;
        }
    }
    std::cout << "End scope" << std::endl;

    if (wPtr.expired())
    {
        std::cout << "wPtr isn't valid" << std::endl;
    }
    else
    {
        std::cout << "wPtr is valid" << std::endl;
    }

    return 0;
}

출력 결과

Begin scope
A() | num: 10
count: 1
wPtr is valid
~A() | num: 10
End scope
wPtr isn't valid

순환참조 방지

클래스에서 멤버 변수로 shared_ptr을 가지게 되면 순환 참조로 인한 메모리 누수가 발생할 수 있다고 했다. 하지만 아래와 같이 weak_ptr을 활용하게 되면, 그리고 weak_ptr로 객체에 접근할 때만 lock을 통해 shared_ptr을 생성하게 되면, 메모리 누수를 방지 할 수 있게 된다.

#include <iostream>
#include <memory>
class Cat
{
public:
    Cat()
    {
        std::cout << "Cat()" << std::endl;
    }
    ~Cat()
    {
        std::cout << "~Cat()" << std::endl;
    }

    // std::shared_ptr<Cat> mFriend;
    std::weak_ptr<Cat> mFriend; // shared_ptr 에서 weak_ptr로 수정
};

int main()
{
    std::shared_ptr<Cat> Jo = std::make_shared<Cat>();
    std::shared_ptr<Cat> Moo = std::make_shared<Cat>();

    Jo->mFriend = Moo;
    Moo->mFriend = Jo;
}

이상으로 스마트 포인터에 대한 정리를 마친다.

 

<이전 글 Smart Pointer의 이해 - ② shared_ptr의 개념과 활용


참조:

유튜브 채널 - 코드없는 프로그래밍: https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg

길벗 출판사 - C++14 STL 철저 입문