728x90

 

Shared Pointer를 이용하여 Circular Reference를 하여 Memory Leak이 발생할 수 있다고 공부를 했다. Weak Pointer의 개념에 대해서 공부하고 이를 이용하여 Circular Reference문제를 해결해 보자. 

 

 

Weak Pointer (위크 포인터)

 

Weak Pointer는 말 그대도 약간 포인터이다. Shared Pointer를 참조하는 용도로 사용을 하게 되고 Weak Pointer가 Shared Pointer를 참조를 하게 되면 Weak Count가 증가하게 된다. Weak Pointer를 이용하여 오브젝트의 리소스를 접근할 수 없다. Weak Pointer를 사용을 하려면 반드시 Shared Pointer형을 반환해 주는 lock() 메서드를 이용하여 Shared Pointer로 변환을 해야 되는데 이때 참조하는 Shared Pointer의 Strong Count가 증가하게 된다는 것을 잊으면 안 된다. 

 

아래는 Weak Pointer가 선언되고 Shared Pointer를 참조하는 코드이다. 

 

#include <iostream>
#include <memory>

class Cat
{
public:
    Cat(std::string name) : mName{ name }
    {
        std::cout << mName << " cat constructor" << std::endl;
    }
    virtual void speak() const
    {
        std::cout << "Hi?" << std::endl;
    }
    ~Cat()
    {
        std::cout << mName << " cat destructor" << std::endl;
    }
    std::shared_ptr<Cat> mVar;
private:
    std::string mName;
};


int main()
{
    std::shared_ptr<Cat> kitty = std::make_shared<Cat>("kitty");
    std::weak_ptr<Cat> weak_kitty = kitty;
    //weak pointer 선언 및 shared pointer kitty참조
    std::cout << "count : " << kitty.use_count() << std::endl;
    
    return 0;
}

 

위 코드를 보면 Shared Pointer kitty를 선언하고 Cat 오브젝트를 생성하였다. 그다음 Weak Pointer를 선언하여 kitty가 가리키고 있는 오브젝트를 참조를 하였다. 그다음 kitty가 가리키고 있는 오브젝트의 count를 출력한 결과 1이 출력이 되었다. 

 

만약 Weak Pointer가 참조하고 있는 오브젝트의 리소스를 사용하고 싶다면 lock() 메서드를 통해 Shared Pointer를 반환받을 수 있다. 아래 코드는 Weak Pointer의 lock() 메서드를 통해 새로운 Shared Pointer를 생성하여 리소스를 사용하는 코드이다. 

 

#include <iostream>
#include <memory>

class Cat
{
public:
    Cat(std::string name) : mName{ name }
    {
        std::cout << mName << " cat constructor" << std::endl;
    }
    virtual void speak() const
    {
        std::cout << "Hi?" << std::endl;
    }
    ~Cat()
    {
        std::cout << mName << " cat destructor" << std::endl;
    }
    std::shared_ptr<Cat> mVar;
private:
    std::string mName;
};


int main()
{
    std::shared_ptr<Cat> kitty = std::make_shared<Cat>("kitty");
    std::weak_ptr<Cat> weak_kitty = kitty;
    
    if(const auto shared_kitty = weak_kitty.lock())
    {
        shared_kitty->speak();
    }
    else
    {
        std::cout << "pointing nothing\n";
    }
    
    return 0;
}

 

위 코드는 if문 조건문에 const auto shared_kitty에 Weak Pointer weak_kitty의 lock() 메서드를 통하여 Shared Pointer를 반환해주었다. 만약 가리키고 있는 Shared Pointer가 메모리 해제가 된 경우 empty shared_ptr를 반환해준다. 위 경우 weak_kitty가 가리키고 있는 kitty 오브젝트는 마메로 해제가 안되었기 때문에 해당 shared_pointer를 반환한다. if 조건문을 scope로 가지는 shared_kitty가 kitty 오브젝트를 가리키고 있기 때문에 kitty의 count가 2로 증가하게 되고 조건문이 끝나고 다시 1로 감소된다. 

 

그렇다면 아래 코드를 보자.

 

#include <iostream>
#include <memory>

class Cat
{
public:
    Cat(std::string name) : mName{ name }
    {
        std::cout << mName << " cat constructor" << std::endl;
    }
    virtual void speak() const
    {
        std::cout << "Hi?" << std::endl;
    }
    ~Cat()
    {
        std::cout << mName << " cat destructor" << std::endl;
    }
    std::shared_ptr<Cat> mVar;
private:
    std::string mName;
};


int main()
{
    std::weak_ptr<Cat> weak_kitty;
    {
        std::shared_ptr<Cat> kitty = std::make_shared<Cat>("kitty");
        weak_kitty = kitty;
    }
    
    if(const auto shared_kitty = weak_kitty.lock())
    {
        shared_kitty->speak();
    }
    else
    {
        std::cout << "pointing nothing\n";
    }
    
    return 0;
}

 

위 코드는 Weak Pointer는 main함수 scope에 선언이 되었고 Shared Pointer인 kitty는 Curly brace scope에 선언이 되었다. 먼저 괄호 안에 선언된 kitty에 Cat 오브젝트를 생성하고 main함수 scope에 있는 weak_kitty를 kitty오브젝트를 참조하게 했다. 괄호를 벗어나고 if문에서 weak_kitty의 lock() 메서드로 Shared Pointer를 반환하려고 했지만 kitty 오브젝트는 이미 괄호 안에서 할당 해제가 되었기 때문에 empty Share Pointer가 반환되어 pointing nothing을 출력하게 된다. 

 

 

Circular Reference (순환 참조) 해결 

 

#include <iostream>
#include <memory>

class Cat
{
public:
	Cat(std::string name) : mName{ name }
	{
		std::cout << mName << " cat constructor" << std::endl;
	}
	~Cat()
	{
		std::cout << mName << " cat destructor" << std::endl;
	}
	std::shared_ptr<Cat> mVar;
private:
	std::string mName;
};

int main()
{
	std::shared_ptr<Cat> kitty = std::make_shared<Cat>("kitty");
	std::shared_ptr<Cat> nabi = std::make_shared<Cat>("nabi");

	kitty->mVar = nabi;
	nabi->mVar = kitty;

	std::cout << "kitty count : " << kitty.use_count() << std::endl;
	std::cout << "nabi count : " << nabi.use_count() << std::endl;
}

 

저번 포스팅에서 다루었던 오브젝트 내부의 Shared Pointer가 서로 다른 오브젝트를 가리키게 되어 count가 줄지 않아 메모리 해제가 안되었던 문제를 Weak Pointer로 해결할 수 있다. 단순하게 클래스 내부의 Shared Pointer를 Weak Pointer로 교체를 하면 문제는 해결된다. 

 

#include <iostream>
#include <memory>

class Cat
{
public:
	Cat(std::string name) : mName{ name }
	{
		std::cout << mName << " cat constructor" << std::endl;
	}
	~Cat()
	{
		std::cout << mName << " cat destructor" << std::endl;
	}
	std::weak_ptr<Cat> mVar;
private:
	std::string mName;
};

int main()
{
	std::shared_ptr<Cat> kitty = std::make_shared<Cat>("kitty");
	std::shared_ptr<Cat> nabi = std::make_shared<Cat>("nabi");

	kitty->mVar = nabi;
	nabi->mVar = kitty;

	std::cout << "kitty count : " << kitty.use_count() << std::endl;
	std::cout << "nabi count : " << nabi.use_count() << std::endl;
}

 

이렇게 코드를 바꾸게 되면 count는 증가하지 않고 main함수 스코프를 가진 kitty와 nabi가 할당 해제가 되면서 각 오브젝트로 메모리 해제가 된다. 

 

 

Ref.

 

https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg

https://en.cppreference.com/w/cpp/memory/weak_ptr

+ Recent posts