01. Smart Pointer
# RAII
스마트 포인터를 사용하는 이유는
RAII(Resource Acquisition Is Initialization)이다
RAII는 오브젝트와 리소스의 Life Cycle을 일치시키는 것을 의미하고
여기서 리소스란 Heap memory, Thread, file access, mutex, db Connection과 같은
한정된 리소스를 의미한다
RAII 중 Heap Memory(리소스)를 관리해 주는
스마트포인터(오브젝트)를 통해 Memory Leak을 근본적으로 없앨 수 있다
# Memory Leak
new를 통해 힙메모리에 데이터를 할당하고
delete를 해주지 않고 프로그램이 종료된다면,
스택에서 포인터만 없어지고 힙에 할당된 데이터는 영원히 남게 되고
이를 Memory Leak이라고 한다
# 스마트 포인터의 특징
스마트포인터를 사용해서 데이터를 할당하면
스마트포인터가 스택에서 해제될 때, 동시에 힙에 올라간 오브젝트도 해제시켜 준다
array의 경우에는 vector를 이용하면 스마트 포인터와 같은 역할을 한다
스마트 포인터는 scope단위로 Lifetime이 적용되어 만들어지고 삭제된다
따라서, 함수 scope 내에 작성한다면 함수의 scope 단위에 따라 Lifetime이 적용된다
02. Unique Pointer
exclusive ownership을 제공해 주는 포인터이다
하나의 오브젝트를 단 하나의 포인터로만 가리키게 한다는 것이다
단, move를 통해 소유권을 빼앗는다면
다른 포인터로 가리키게 할 수 있다
주로 클래스의 멤버 변수로
포인터를 가져야 할 경우에 많이 사용한다
그 예시로는 dynamic polymorphism을 위해서
멤버 변수로 포인터를 가져야 할 경우이다
클래스의 멤버 변수에 포인터형이 있다면,
rule of three 혹은 five라는 regulation에 따라서
개발자가 직접 destructor, copy/move constructor, copy/move assignment를
만들어야 하고
해당 멤버 포인터 오브젝트는 클래스 밖에서 해당하는 클래스 포인터가
가리킬 수도 있다
반면, 유니크 포인터를 이용한다면
다른 포인터가 가리킬 수 없고
destructor에서의 메모리 해제를 신경 안 써도 되고
copy가 불가능해지니 copy constructor/assignment를 만들어 줄 필요도 없고
move constructor/assignment는 자동으로 컴파일러가 만들어주기에 신경을 쓰지 않아도 된다
#include <iostream>
#include <memory>
class Cat : public Animal
{
public:
Cat():mAge{0}
{
std::cout << "cat constructor" << std::endl;
}
~Cat()
{
std::cout << "cat destructor" << std::endl;
}
private:
int mAge;
};
class Dog : public Animal
{};
class Zoo
{
public:
//prefer enum class
Zoo(int n)
{
if(n==1)
{
mAnimal = make_unique<Cat>()
}
else
{
mAnimal = make_unique<Dog>()
}
}
private:
std::unique_ptr<Animal> mAnimal;
};
int main()
{
// Cat * catPtr = new Cat();
// Cat * catPtr1 = catPtr;
std::unique_ptr<Cat> catPtr = std::make_unique<Cat>();
std::unique_ptr<Cat> catPtr1 = std::move(catPtr);
// delete catPtr;
return 0;
}
03. Shared Pointer
shared ownership을 제공해 주는 포인터이다
Reference count를 통해 데이터를 언제 해제해야 하는지 알 수가 있다
Shared Pointer의 경우 순환 참조(서로를 참조)를 하고 있는 경우에는
아래와 같이 메모리 릭이 발생할 수 있다
#include <iostream>
#include <memory>
class Cat
{
public:
Cat()
{
std::cout << "cat constructor" << std::endl;
}
~Cat()
{
std::cout << "cat destructor" << std::endl;
}
std::shared_ptr<Cat> mFriend;
};
int main()
{
std::shared_ptr<Cat> pKitty = std::make_shared<Cat>();
std::shared_ptr<Cat> pNabi = std::make_shared<Cat>();
pKitty->mFriend = pNabi;
pNabi->mFriend = pKitty;
return 0;
}
04. Weak Pointer
# Weak Pointer
Shared Pointer처럼 작동을 하지만
Reference count에는 영향을 주지 않는 스마트 포인터이다
Weak Pointer 오브젝트를 사용하기 위해서는
반드시 Shared Pointer로 반환을 받아서 사용해야 한다
전환을 해서 사용을 한다면 Ref count에 영향을 준다
Weak Pointer가 valid 한 오브젝트를 가리키고 있는지는
lock을 사용하거나
use_count가 1 이상 인지 확인하거나
expired 함수가 false를 return 해주는지 확인하는 방법이 있다
Shared Pointer의 순환 참조의 메모리 릭을 피하기 위해서는
Weak pointer로 바꿔주면 된다
만약에 다른 종류의 클래스 간의 순환 참조가 발생한다면,
A 클래스가 B 클래스를 weak pointer로 참조하고,
B 클래스는 A 클래스를 shared pointer로 참조하도록 설계하면 된다
#include <iostream>
#include <memory>
class Cat
{
public:
Cat()
{
std::cout << "cat constructor" << std::endl;
}
~Cat()
{
std::cout << "cat destructor" << std::endl;
}
//std::shared_ptr<Cat> mFriend;
std::weak_ptr<Cat> mFriend;
};
int main()
{
std::shared_ptr<Cat> pKitty = std::make_shared<Cat>();
std::shared_ptr<Cat> pNabi = std::make_shared<Cat>();
std::cout << pKitty.use_count();
std::cout << pNabi.use_count() << std::endl;
// mFriend가 shared_ptr이라면 1, 1 출력
// mFriend가 weak_ptr이라면 1, 1 출력
pKitty->mFriend = pNabi;
pNabi->mFriend = pKitty;
std::cout << pKitty.use_count();
std::cout << pNabi.use_count() << std::endl;
// mFriend가 shared_ptr이라면 2, 2 출력
// mFriend가 weak_ptr이라면 1, 1 출력
std::weak_ptr<Cat> wPtr;// weak_ptr 사용하려면
{
std::shared_ptr<Cat> sPtr = std::make_shared<Cat>();
wPtr = sPtr;// shared_ptr로 변환
}
if (const auto spt = wPtr.lock())// weak_ptr valid여부 확인
{
std::cout << spt.use_count() << std::endl;
}
else
{
std::cout << "point nothing" << std::endl;
}
return 0;
}
# Unique Pointer와 Shared Pointer를
멤버 변수로 가지고 있는 경우 발생하는 문제
1) Unique Pointer를 멤버 변수로 가지고 있는 경우
컴파일러가
Unique Pointer의 exclusive ownership을 위반하지 않게
Copy constructor를 막는다
따라서 Copy constructor를 우리가 직접 만들어 줘야 한다
#include <iostream>
#include <memory>
class Cat
{
public:
explicit Cat(int n):mVar{ std::make_unique<int>(n) }
{
std::cout << "cat constructor" << std::endl;
}
Cat(const Cat& other) :mVar{ std::make_unique<int>(*other.mVar) }
{
std::cout << "cat copy constructor" << std::endl;
}
~Cat() noexcept
{
std::cout << "cat destructor" << std::endl;
}
private:
std::unique_ptr<int> mVar;
};
int main()
{
const Cat kitty{1};
const Cat nabi{kitty};
return 0;
}
2) Shared Pointer를 멤버 변수로 가지고 있는 경우
컴파일러가
Copy constructor/assignment를 막아주지 않는다
하지만 모든 클래스가 같은 하나의 공간을 사용한다
이러한 상황을 의도적으로 만들었음을 알려주기 위해
클래스 위에 반드시
"// A has Shared Ptr member variable"과 같은 주석 표시를 해야 한다
또한,
clone 함수를 만들어서 deep copy interface를 제공하는 것이 좋다
#include <iostream>
#include <memory>
//Cat has Shared Ptr member variable
class Cat
{
public:
explicit Cat(int n) :mVar{ std::make_shared<int>(n) }
{
std::cout << "cat constructor" << std::endl;
}
~Cat() noexcept
{
std::cout << "cat destructor" << std::endl;
}
Cat clone() const//create deep copied object
{
Cat tmp{ *mVar };
return tmp;
}
private:
std::shared_ptr<int> mVar;
};
int main()
{
const Cat kitty{ 2 };
const Cat nabi{ kitty.clone() };
return 0;
}
'C++ > [노코프] C++' 카테고리의 다른 글
[C++ NOTE] Functional Programming (0) | 2024.03.31 |
---|---|
[C++ NOTE] Template (0) | 2024.03.30 |
[C++ NOTE] Inheritance (0) | 2024.03.28 |
[C++ NOTE] OOP (0) | 2024.03.27 |
[C++ NOTE] Resource Move (0) | 2024.03.26 |