smart pointer
-
[C++] Smart Pointer Intro (스마트 포인터)2021.08.22
[C++] Weak Pointer (위크 포인터) : Circular Reference (순환 참조) 해결
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.
'Modern C++' 카테고리의 다른 글
[C++] Template Type Deduction (템플릿 타입 추론) : Perfect Forwarding (0) | 2021.08.26 |
---|---|
[C++] Template Intro. (템플릿) : Function Template (함수 템플릿) (0) | 2021.08.26 |
[C++] Shared Pointer (쉐어드 포인터) : Circular Reference (순환 참조) (0) | 2021.08.25 |
[C++] Smart Pointer (스마트 포인터): Unique Pointer (유니크 포인터) (0) | 2021.08.23 |
[C++] Smart Pointer Intro (스마트 포인터) (0) | 2021.08.22 |
[C++] Smart Pointer (스마트 포인터): Unique Pointer (유니크 포인터)
Smart Pointer에서 간단하게 Smart Pointer에 대해서 알아보았다. 이번에는 스마트 포인터의 한 종류인 Unique Pointer (유니크 포인터)에 대해서 공부한다.
Unique Pointer (유니크 포인터)
유니크 포인터에 대해서 공부하기 전에 유니크 포인터의 Exclusive Ownership이 뭔지 얘기하고 시작한다.
Exclusive Ownership (소유권 독점)
소유권 독점이란 하나의 Object에 단 하나의 Pointer만 가리킬 수 있다는 것이다. 말 그래로 소유권을 독점한다라는 뜻이다. 일반적인 포인터를 사용하게 되면 한 Object당 여러 개의 포인터가 가리킬 수 있는데 Exclusive Ownership은 아니다.
아래
코드는 하나의 객체에 다수의 포인터가 가리킬 경우 생길 수 있는 문제 중 하나이다.
#include <iostream>
class Cat
{
public:
Cat() : mAge{ 0 }
{
std::cout << "cat constructor" << std::endl;
}
~Cat()
{
std::cout << "cat destructor" << std::endl;
}
private:
int mAge;
};
void memoryDealloc(Cat* dealloc)
{
delete dealloc;
}
int main()
{
Cat* choco = new Cat();
memoryDealloc(choco);
delete choco;
}
choco객체를 동적으로 선언을 해주고 메모리를 해제해주는 memoeyDealloc함수를 만들어 delete 해준다. 이 과정에서 main함수에서 실수로 또 delete를 해주게 된다면 한 메모리 공간에 대해서 delete를 두 번 해주는 상황이 발생한다.
이러한 문제를 방지하기 위해서 스마트 포인터를 사용하면 된다.
int main()
{
std::unique_ptr<Cat> catPtr = std::make_unique<Cat>();
std::unique_ptr<Cat> catPtr1 = catPtr; //ERROR
}
main함수를 스마트 포인터를 이용하여 다시 고쳐 쓰면 위와 같은데 위 코드에서 스마트 포인터 catPtr과 catPtr1이 선언이 되고 catPtr1는 catPtr이 가리키고 있는 객체의 주소 값을 복사해온다. 하지만 Exclusive Ownership특성 때문에 하나의 객체를 여러 개의 포인터가 가리킬 수 없기 때문에 에러가 난다. 하지만 catPtr1에 해당 객체의 주소 값을 저장하고 싶다면 객체의 소유권을 뺏어오면 된다.
int main()
{
std::unique_ptr<Cat> catPtr = std::make_unique<Cat>();
std::unique_ptr<Cat> catPtr1 = std::move(catPtr);
}
위와 같이 dogPtr을 R-Value로 바꾸고 dogPtr1에 소유권을 이전하면 문제가 생기지 않는다.
스마트 포인터 사용 예
Animal Class와 Zoo Class를 추가로 만든다. 그다음 막 개장한 동물원이라서 동물원 안에 동물이 하나밖에 없는 상황이라고 가정을 하면 Zoo Class에 멤버 변수로 Animal 객체 하나만 가지게 된다. 원래 스마트 포인터를 사용하지 않았다면 Rule og Three에 의해서 Destructor, Copy / Move Constructor와 Copy / Move Assignment를 만들어줘야 하지만 스마트 포인터를 이용하면 알아서 scope 단위에서 메모리를 해제해주고 Copy Constructor와 Assignment는 애초에 스마트 포인터가 copy가 안되기 때문에 만들어 주지 않아도 되고 Mov Constructor와 Assignment는 컴파일러가 만들어 주는 함수로 커버가 가능하기 때문에 스마트 포인터를 사용하게 되면 일반 멤버 변수처럼 클래스를 구성해도 무방하다.
'Modern C++' 카테고리의 다른 글
[C++] Weak Pointer (위크 포인터) : Circular Reference (순환 참조) 해결 (0) | 2021.08.25 |
---|---|
[C++] Shared Pointer (쉐어드 포인터) : Circular Reference (순환 참조) (0) | 2021.08.25 |
[C++] Smart Pointer Intro (스마트 포인터) (0) | 2021.08.22 |
[C++] Object Slicing (오브젝트 슬라이싱) (0) | 2021.08.22 |
[C++] Virtual Inheritance (가상 상속) : Diamond Problem (다중 상속 문제) 해결 (0) | 2021.08.21 |
[C++] Smart Pointer Intro (스마트 포인터)
우리는 동적 할당을 Type* ptr = new Type;과 같이 선언하였다. 동적 할당을 할 때 가장 신경 써야 할 부분은 바로 Memory Leak (메모리 누수)이다. 왜냐하면 동적으로 할당된 메모리는 delete키워드를 통해 해제시켜줘야 한다는 단점이 있기 때문이고 우리는 delete를 까먹게 되는 경우가 많다. 이를 해소하기 위해 C++11부터 Smart Pointer을 이용하여 자동적으로 메모리 해제를 해주는 기능이 나왔다.
C++ 에는 RAII (Resouce Acquisition Is Initialization)이 있다. RAII가 생긴 이유는 타 언어들과 다르게 GC (garbage collector)가 존재하지 않기 때문이다 (있기는 하나 사용을 안 한다). 우리가 결국 원하는 건 메모리 누수가 나지 않는 것인데 RAII를 통하여 얻을 수 있다. 결국 RAII란 자원의 안전한 사용을 위해서 scpoe가 끝나면 메모리 해제를 해주는 기법이다.
Smart Pointer (스마트 포인터)
Memory Leak은 정말 짜증 나는 경우이다. 아무 생각 없이 동적 할당을 난발하면 메모리 릭이 발생하는데 나중에 어디서 메모리 릭이 발생하는 건지 찾고 수정하는 데에 시간이 오래 걸린다. 이를 해소하기 위해 Smart Pointer가 등장했다.
Smart Pointer를 선언하는 방법은 아래 코드와 같다.
#include <iostream>
#include <memory>
class Dog
{
public:
Dog(int age) : mAge{ age }
{
std::cout << "Dog constructor" << std::endl;
}
~Dog()
{
std::cout << "Dog destructor" << std::endl;
}
private:
int mAge;
};
int main()
{
std::unique_ptr<Dog> dogPtr = std::make_unique<Dog>(9);
return 0;
}
스마트 포인터를 사용하기 위해서는 <memory> 헤더를 include를 시켜주어야 한다. 그다음 main함수 첫 번째 줄과 같이 선언해주면 된다. 위 코드를 돌려보면 자동으로 Comstructor와 Destructor가 불리는 것을 확인할 수 있다.
스마트 포인터는 scope단위로 scope가 끝나면 메모리를 자동으로 해제해준다.
Into이기 때문에 간단하게 설명하고 다른 포스트에서 더 자세하게 다룬다.
Ref.
'Modern C++' 카테고리의 다른 글
[C++] Shared Pointer (쉐어드 포인터) : Circular Reference (순환 참조) (0) | 2021.08.25 |
---|---|
[C++] Smart Pointer (스마트 포인터): Unique Pointer (유니크 포인터) (0) | 2021.08.23 |
[C++] Object Slicing (오브젝트 슬라이싱) (0) | 2021.08.22 |
[C++] Virtual Inheritance (가상 상속) : Diamond Problem (다중 상속 문제) 해결 (0) | 2021.08.21 |
[C++] Multiple Inheritance (다중 상속) (0) | 2021.08.21 |