0. Smart Pointer의 메모리 공간
- 스마트 포인터는 힙 메모리 공간과 스택 메모리공간을 모두 사용한다고 한다.
- 스마트 포인터 자체는 스택 메모리 공간에 저장된다.
- 함수 안에서 선언한 스마트 포인터는 함수가 종료되면, 스택에서 사라지고, 메모리도 해제된다
- 아래의 코드를 실행하면, 생성자와 소멸자 모두 호출된다.
int main()
{
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
}
}
- 그러나 스마트 포인터가 관리하는 실제 리소스는 힙 메모리에 저장된다.
- 아래의 코드처럼 실행하면, std::cin.get(); 이 실행되기 전까지 소멸자가 호출되지 않는다. (좋은 코드는 아닐 것 같음)
- 이유는 e0가 sharedEntity의 값을 공유했기 때문에, Func() 함수는 끝났지만, 아직 생명 주기는 남아있다.
- 결과적으로 모든 코드가 종료되면, 그때 소멸자가 호출된다.
- 결론적으로 reference count 가 0이 되어야만 소멸자가 호출된다.
std::shared_ptr<Entity> e0;
void Func()
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
e0 = sharedEntity;
}
int main()
{
Func();
std::cin.get();
}
1. shared_ptr
- 아래 코드를 실행한 결과를 보면 다음과 같다.
- 위에서 언급한 것과 비슷한 내용으로 sharedEntity는 { } 밖으로 나와 생명주기가 끝났지만, e0 때문에 소멸자가 호출되지 않고 바깥 { } 에서 나와야 소멸자가 호출된다.
#include <iostream>
#include <memory>
class Entity
{
public:
Entity()
{
std::cout << "constructor\n";
}
~Entity()
{
std::cout << "destructor\n";
}
void Print()
{
}
};
int main()
{
{
std::shared_ptr<Entity> e0;
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
e0 = sharedEntity;
}
std::cout << "...\n";
}
std::cin.get();
}
2. weak_ptr
- weak_ptr은 shard_ptr의 값을 공유할 수 있지만, reference count는 늘리지 않는다.
- 아래의 코드를 실행시키면, e0를 shared_ptr로 만들었을 때의 결과물과 다른 것을 볼 수 있다.
- 그 이유는 reference count를 늘리지 않았기 때문에 안쪽 { }를 빠져나오면서 reference count 가 1에서 0이 되고, 소멸자가 호출된 것이다.
int main()
{
{
std::weak_ptr<Entity> e0; //weak_ptr !!
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
e0 = sharedEntity;
}
std::cout << "...\n";
}
std::cin.get();
}
3. 그렇다면 언제 써야할까
- 정확한 답은 아니지만, 구글링을 하던 도중 유튜브에서 찾은 내용을 번역해 보았다.
- 출처:https://www.youtube.com/watch?v=UOB7-B2MfwA
1. 절대 new와 delete를 사용하지 마세요. 포인터가 객체를 소유하고 있는지 여부는 매우 명확해야 합니다. 만약 포인터가 객체를 소유하고 있다면(예를 들어, 객체를 생성하고 있다면), 스마트 포인터를 사용하여 make_unique 또는 make_shared를 사용하세요.
2. 기본적으로 오버헤드가 거의 없기 때문에 unique_ptr을 사용하세요. 여러 소유자가 있어야 한다는 것을 알고 있다면 shared_ptr을 사용하세요.
3. 포인터가 객체를 소유하지 않을 때는 원시 포인터를 사용하세요. 예를 들어, 객체를 함수에 전달할 때 수신한 포인터는 객체를 소유하지 않으므로(함수가 반환되면 객체는 여전히 살아 있을 것입니다) 스마트 포인터를 전달하지 말고 원시 포인터를 전달하세요.
함수의 원시 포인터로 unique_ptr을 전달하려면, 가장 좋은 방법은 참조를 해제하고 참조로 전달하는 것입니다. 따라서 함수는 예를 들어 "void foo(const Class & myObject)"이며, "foo(*myPointer)"라고 부릅니다.
다른 방법으로는 "unique_ptr.get()" 메서드를 사용하여 주소 자체를 전달하는 것입니다.
4. 다시 말해서, 원시 포인터에서 delete를 사용하지 말고 객체를 소유하지 않는다고 가정하세요.
5. 함수 내부에 객체를 생성하고 반환하려면 unique_ptr로 수행합니다. receiver는 함수를 사용하여 원하는 모든 작업을 수행할 수 있습니다. 이제 함수에서 로컬 객체를 복사하여 전달하면 실제로 복사하는 것이 아니라 r-value로 이동한다는 점을 명심하세요. 따라서 컴파일러 오류 없이 새로운 unique_ptr로 이동합니다. 다시 말하지만, receiver는 원하는 모든 작업을 수행할 수 있으므로 shared_ptr 또는 원시 포인터로 이동할 수 있습니다.
6. unique_ptr의 소유권을 이전하고 싶을 때는 std::move()를 사용하여 r-value로 할 수 있습니다. 이것이 기본적으로 함수에서 돌아올 때 C++가 하는 일이라고 설명한 것입니다. 함수로 소유권을 이전하는 데 사용할 수 있습니다.
- 아직 명확하게 감이 잡히진 않기에, 나중에 직접 사용하게 되는 경우가 있다면 그 때 느낀 점들을 추가적으로 기록해 두어야 할 거 같다.
'C++' 카테고리의 다른 글
C++ TIL day 9 (3) | 2024.12.27 |
---|---|
C++ TIL day 8 (0) | 2024.12.26 |
C++ TIL day 7 (2) | 2024.12.24 |
C++ 상속 (0) | 2024.12.23 |
C++ TIL day 6 (0) | 2024.12.23 |