-
C++ 프로그램에서 new 키워드를 사용하여 동적으로 할당받은 메모리는, 반드시 delete 키워드를 사용하여 해제해야 합니다.
-
C++에서는 메모리 누수(memory leak)로부터 프로그램의 안전성을 보장하기 위해 스마트 포인터를 제공하고 있습니다.
-
스마트 포인터(smart pointer)란 포인터처럼 동작하는 클래스 템플릿으로, 사용이 끝난 메모리를 자동으로 해제해 줍니다.
-
C++11에서 새롭게 도입된 스마트 포인터는 unique_ptr, shared_ptr, weak_ptr입니다.
-
unique_ptr 실제 데이터에 대한 소유권을 오직 하나만 가지도록 하는 것
-
shared_ptr 실제 데이터에 대한 소유권을 여러 개의 스마트포인터가 공유하면서 참조 카운트 방식으로 관리
-
weak_ptr share_ptr 실제 데이터를 공유하지만 참조 카운트의 값을 증가하거나 감소시키지 않는다
1.unique_ptr
unique_ptr
- 하나의 스마트 포인터만이 특정 객체를 소유할 수 있도록, 객체에 소유권 개념을 도입한 스마트 포인터입니다.
- 이 스마트 포인터는 해당 객체의 소유권을 가지고 있을 때만, 소멸자가 해당 객체를 삭제할 수 있습니다.
- 순환 참조 문제를 해결하기 위해서 참조가 단 1개만 존재하는 스마트 포인터
- unique_ptr 인스턴스는 move() 멤버 함수를 통해 소유권을 이전할 수는 있지만, 복사할 수는 없습니다.(Move::이동시퀸스로 이전)
- 소유권이 이전되면, 이전 unique_ptr 인스턴스는 더는 해당 객체를 소유하지 않게 재설정됩니다.
보통의 C++ 객체에 대해 스마트 포인터가 필요한 상황에서는 주로 unique_ptr을 사용하면 됩니다.
- std::unique_ptr는 복사 생성자와 복사 대입 연산자가 아예 구현되어 있지 않다.
- 그렇기 때문에 복사가 애초에 불가능하고 이동만 가능하다.
- 이동은 std::move() 함수로만 가능하다.
- 포인터는 get() 멤버 함수로 얻을 수 있고 메모리 해제는 reset() 멤버 함수로 한다.
포인터를 복사할 수 없는거지 포인터가 가리키는 객체를 복사할 수 없는 것은 아니다. 또 복사 대입 연산시 좌항을 참조자로 선언하고 정의하는 것도 포인터의 포인터를 통해 참조하는 것이기 때문에 가능하다.
std::unique_ptr<int> foo(new int(3));
auto bar = *foo;
auto& baz = foo;
bar = 0; //bar =0으로 되고 , foo는 변화없음
baz = 0; //baz -> empty , foo ->empty 됨
const std::unique로 선언된 포인터는 std::move()로도 이동시킬 수 없다.
비트 수준의 상수성을 가지고 있기 때문에 데이터를 다른 곳으로 이동을 시킬 수 없어서 그렇다.
const std::unique_ptr<int> foo(new int(3));
auto bar = std::move(foo); //컴파일 에러
std::unique_ptr<int> foo(new int(3));
auto bar = std::move(foo); //정상
2.shared_ptr
std::shared_ptr는 이름처럼 가리키는 객체의 소유권을 다른 포인터들과 공유할 수 있는 포인터다.
- std::unique_ptr과는 다르게 복사도 마음껏 할 수 있다.
- 같은 객체를 가리키는 std::shared_ptr은 레퍼런스 카운팅으로 추적된다.
- 참조된 횟수를 세는 것이므로 포인터가 복사될 때 마다 1씩 증가한다. 그리고 해제될 때 마다 1씩 감소한다.
포인터가 가리키는 객체의 메모리가 해제되는 시점은 레퍼런스 카운트가 0이 될 때이다. - 레퍼런스 카운트는 use_count() 멤버 함수로 가져올 수 있다.
- make_shared는 (custom) deleter를 사용 할수가 없다
- 공유자원에 좋음?
std::shared_ptr 객체가 복사되어도 메모리 공간은 늘어나지 않는다.
객체의 메모리 공간은 그대로 두고, 레퍼런스 카운트만 건드리기 때문에 그렇다.
void main() {
std::shared_ptr<int> foo(new int(3)); // reference count = 1
auto bar = foo; // reference count = 2
foo.reset(); // reference count = 1
bar.reset(); // reference count = 0, 이떄 객체가 완전히 해제된다.
}
레퍼런스 카운트가 0이 되어 참조하는 객체를 해제할 때 std::shared_ptr는 delete 연산자를 사용한다.
여기에 문제가 있다. delete[]는 사용하지 않는 것이 문제다.
해결방법은 아래처럼, 벡터에 shard_ptr을 넣거나, Deleter를 만드는 것이다
int main() {
std::shared_ptr<int> foo(new int[1024]); // 이렇게 배열의 포인터를 선언하지 말고
foo.reset();
std::vector<std::shared_ptr<int>> bars; // 포인터 벡터를 만들면 해결된다.
bars.push_back(std::shared_ptr<int>(new int(3)));
for(auto& bar : bars) {
bar.reset();
//삭제될떄 delete 가 됨
}
}
std::shared_ptr의 기본 생성자
constexpr shared_ptr() noexcept;
constexpr shared_ptr( std::nullptr_t ) noexcept;
template< class Y >
explicit shared_ptr( Y* ptr );
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
template< class Deleter >
shared_ptr( std::nullptr_t ptr, Deleter d ); //Deleter 함수 객체 정의 가능
template< class Y, class Deleter, class Alloc >
shared_ptr( Y* ptr, Deleter d, Alloc alloc );
Deleter 예시
template<typename T>
struct ArrayDeleter {
void operator() (T* ptr) {
delete[] ptr;
}
};
void main() {
std::shared_ptr<int> foo(new int[1024], ArrayDeleter<int>());
foo.reset(); // delete[]을 한다. 이제 배열 제대로 해제될 것이다.
}
샘플
shared_ptr<int> Ashard(new int(33),ArrayDeleter<int>());
//shared_ptr<int> Bshard = make_shared<int>(55, ArrayDeleter<int>()); make_shard는 deleter 안됨
shared_ptr<int> Bshard(new int(33));
shared_ptr<int> Cshard = std::move(Ashard); //A->empty 됨
Bshard.reset(); //B->empty됨
Cshard.reset(); //C->empty됨
'프로그래밍 > C++' 카테고리의 다른 글
이동 시멘틱 (0) | 2019.04.29 |
---|---|
함수 포인터 (0) | 2019.04.29 |
템플릿 <template> (0) | 2019.04.29 |
OOP 객체지향프로그래밍 (0) | 2019.04.29 |
C++ 11 추가된 기능 (0) | 2019.04.29 |