[Effective C++] 13. 자원 관리에는 객체가 그만!
우리는 동적 할당한 객체를 사용할 일이 더 이상 없을 때 해제해줘야 한다는 사실을 알고 있다.
아래의 예시를 보자.
void f()
{
Investment *pInv = createInvestment();
...
delete pInv;
}
이렇게 하면 f를 벗어날 때 Investment객체를 해제하기 때문에 문제가 없어 보이지만, 중간에 예외가 발생하거나 return 되는 경우 등에 대한 고려는 없음을 알 수 있다.
위와 같은 경우를 막기 위한 방법으로는 자원을 객체에 넣고 그 자원 해제를 소멸자가 맡도록 하고, 소멸자는 실행 제어가 f를 떠날 때 호출되도록 만들면 되는데 이러한 내용을 템플릿 클래스로 만든 것이 스마트 포인터이다.
스마트 포인터
포인터처럼 동작하는 클래스 템플릿으로 사용이 끝난 메모리를 자동으로 해제해 준다.
책에는 auto_ptr과 shared_ptr이 나오지만 auto_ptr은 C++11부터 삭제되고 새로운 스마트 포인터들을 제공하므로, 여기서는 C++11 이후의 새로운 스마트 포인터들에 대해서 말하겠다.
- unique_ptr
- shared_ptr
- weak_ptr
스마트 포인터는 이렇게 3종류로 memory 헤더 파일에 정의되어 있다.
1. unique_ptr
하나의 스마트 포인터만이 특정 객체를 소유할 수 있도록 객체에 소유권 개념을 도입한 스마트 포인터
이 스마트 포인터는 해당 객체의 소유권을 가지고 있을 때만, 소멸자가 해당 객체를 삭제할 수 있다.
그리고 unique_ptr 인스턴스는 move() 멤버 함수를 통해 소유권을 이전할 수는 있지만 복사할 수는 없으며, 소유권이 이전되면 이전 unique_ptr 인스턴스는 더는 해당 객체를 소유하지 않게 재설정된다.
unique_ptr<int> ptr01(new int(5)); // int형 unique_ptr인 ptr01을 선언하고 초기화함.
auto ptr02 = move(ptr01); // ptr01에서 ptr02로 소유권을 이전함.
그리고 C++14 이후부터는 제공되는 make_unique() 함수를 사용하면 둘 이상의 unique_ptr가 한 객체를 참조하는 상황을 막아 인스턴스를 안전하게 생성할 수 있다.
// 한 객체를 둘 이상의 unique_ptr이 공유
Vecotr * vectorPtr = new Vector(10.f, 30.f);
std::unique_ptr<Vector> vector(vectorPtr);
std::unique_ptr<Vector> anotherVector(vectorPtr);
anotherVector = nullptr;
//make_pair를 통한 문제 해결
#include <memory>
#include "Vector.h"
int main()
{
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f, 30.f);
myVector->Print();
return 0;
}
2. shared_ptr
하나의 특정 객체를 참조하는 스마트 포인터가 총 몇 개인지를 참조하여 참조 횟수가 0이 되면 메모리를 해제하는 스마트 포인터
동작 방식은 가비지 컬렉션과 비슷하나, 순환 참조를 이루는 경우(A가 B를, B가 A를 참조)를 없앨 수 없다는 점은 다르다.
shared_ptr<int> ptr01(new int(5)); // int형 shared_ptr인 ptr01을 선언하고 초기화함
cout << ptr01.use_count() << endl; // 1
auto ptr02(ptr01); // 복사 생성자를 이용한 초기화
cout << ptr01.use_count() << endl; // 2
auto ptr03 = ptr01; // 대입을 통한 초기화
cout << ptr01.use_count() << endl; // 3
3. weak_ptr
하나 이상의 shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공하지만, 소유자의 수에는 포함되지 않는 스마트 포인터
shared_ptr은 참조 횟수를 기반으로 동작하는 스마트 포인터인데, 만약 서로가 상대방을 가리키는 shared_ptr를 가지고 있다면, 참조 횟수는 절대 0이 되지 않으므로 메모리는 영원히 해제되지 않는다.
이렇게 서로가 상대방을 참조하고 있는 상황을 순환 참조라고 하는데 weak_ptr은 바로 이러한 shared_ptr 인스턴스 사이의 순환 참조를 제거하기 위해서 사용된다.
요약
- 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하는 RAII객체를 사용하자.
- 스마트 포인터를 사용하자!
'Books > Effective C++' 카테고리의 다른 글
[Effective C++] 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 (0) | 2022.05.22 |
---|---|
[Effective C++] 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 (0) | 2022.05.22 |
[Effective C++] 12. 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2022.05.18 |
[Effective C++] 11. operator=에서는 자기 대입에 대한 처리가 빠지지 않도록 하자 (0) | 2022.05.18 |
[Effective C++] 10. 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2022.05.14 |
댓글
이 글 공유하기
다른 글
-
[Effective C++] 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자
[Effective C++] 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자
2022.05.22 -
[Effective C++] 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자
[Effective C++] 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자
2022.05.22 -
[Effective C++] 12. 객체의 모든 부분을 빠짐없이 복사하자
[Effective C++] 12. 객체의 모든 부분을 빠짐없이 복사하자
2022.05.18 -
[Effective C++] 11. operator=에서는 자기 대입에 대한 처리가 빠지지 않도록 하자
[Effective C++] 11. operator=에서는 자기 대입에 대한 처리가 빠지지 않도록 하자
2022.05.18