unique_ptr
- C++에서 메모리를 잘못된 방식으로 관리하였을 때 크게 두 가지 종류의 문제가 발생
- 메모리 누수, 메모리를 사용하고 해제 하지 않음 - RALL 패턴을 사용하면 해결 가능
- 해제한 메모리를 다시 참조
Data* data = new Data();
Date* data2 = data;
// data 의 입장 : 사용 다 했으니 소멸시켜야지.
delete data;
// ...
// data2 의 입장 : 나도 사용 다 했으니 소멸시켜야지
delete data2;
- 위 문제가 발생한 이유는 만들어진 객체의 소유권이 명확하지 않았기 때문
- 포인터에 객체의 유일한 소유권을 부여하면 해결 가능
- 이 포인터 외에 객체를 소멸시킬 수 없다고 하면 double free는 발생하지 않음
#include <iostream>
#include <memory>
class A {
int *data;
public:
A()
{
std::cout << "자원을 획득함!" << std::endl;
data = new int[100];
}
void some()
{
std::cout << "일반 포인터와 동일하게 사용가능!" << std::endl;
}
~A()
{
std::cout << "자원을 해제함!" << std::endl;
delete[] data;
}
};
void doSomething() {
std::unique_ptr<A> pa(new A());
pa->some();
}
int main()
{
doSomething();
}
output
자원을 획득함!
일반 포인터와 동일하게 사용가능!
자원을 해제함!
- unique_ptr을 정의하기 위해서는 템플릿에 인자로, 포인터가 가리킬 클래스를 전달하면 된다.
std::unique_ptr<A> pa(new A());
- unique_ptr을 통해 RALL 패턴을 사용할 수 있다.
- pa는 스택에 정의된 객체이기 때문에 doSomethig() 함수가 종료될 때 자동으로 소멸자가 호출
- unique_ptr은 소멸자 안에서 자신이 가리키는 자원을 해제
unique_ptr 소유권 이전
- unique_ptr은 복사가 되지 않지만, 소유권은 이전할 수 있다.
#include <iostream>
#include <memory>
class A {
int *data;
public:
A()
{
std::cout << "자원을 획득함!" << std::endl;
data = new int[100];
}
void some()
{
std::cout << "일반 포인터와 동일하게 사용가능!" << std::endl;
}
~A()
{
std::cout << "자원을 해제함!" << std::endl;
delete[] data;
}
};
void doSomething() {
std::unique_ptr<A> pa(new A());
std::cout << "pa: ";
pa->some();
std::unique_ptr<A> pb = std::move(pa);
std::cout << "pb: ";
pb->some();
}
int main()
{
doSomething();
}
output
자원을 획득함!
pa : 일반 포인터와 동일하게 사용가능!
pb : 일반 포인터와 동일하게 사용가능!
자원을 해제함!
unique_ptr 쉽게 생성하기
- C++14부터 unique_ptr을 간단하게 생성할 수 있는 std::make_unique 함수를 제공
#include <iostream>
#include <memory>
class Foo {
int a, b;
public:
Foo(int a, int b) : a(a), b(b)
{
std::cout << "생성자 호출!" << std::endl;
}
void print()
{
std::cout << "a : " << a << ", b : " << b << std::endl;
}
~Foo()
{
std::cout << "소멸자 호출!" << std::endl;
}
};
int main()
{
auto ptr = std::make_unique<Foo>(3, 5);
ptr->print();
}
output
생성자 호출!
a : 3, b : 5
소멸자 호출!
shared_ptr
- 유일하게 객체를 소유하는 unique_ptr과 다르게, shared_ptr로 가리킬 경우
- 다른 shared_ptr 역사 그 객체를 가리킬 수 있다.
std::shared_ptr<A> p1(new A());
std::shared_ptr<A> p2(p1); // p2 역시 생성된 객체 A 를 가리킨다.
// 반면에 unique_ptr 의 경우
std::unique_ptr<A> p1(new A());
std::unique_ptr<A> p2(p1); // 컴파일 오류!
- 참조 개수(reference count)를 통해 참조 개수가 0이 되면 가리키고 있는 객체를 해제한다.
- shared_ptr은 control block을 통해 shared_ptr들이 필요한 정보를 공유하는 방식으로 구현되었다
- 복사 생성될 때마다 제어 블록의 위치만 공유
- shared_ptr을 사용할 때 주소 값을 넘겨주면 안된다.
make_shared로 생성
std::shared_ptr<A> p1 = std::make_shared<A>();
- 생성자의 인자들을 받아 객체와 shared_ptr의 제어 블록까지 한 번에 동적할당 한 후 shared_ptr을 반환
- 생성자의 인자가 존재할 경우 make_shared에 인자로 전달해 주면 된다.
weak_ptr
- 일반 포인터와 shared_ptr 사이에 위치한 스마트 포인터
- 스마트 포인터 처럼 객체를 안전하게 참조할 수 있지만, shared_ptr과는 다르게 참조 개수를 늘리진 않음
- 어떤 객체를 weak_ptr이 가리키고 있다고 하더라도, 다른 shared_ptr들이 가리키고 있지 않으면 메모리에서 소멸되었을 것
- 따라서 weak_ptr 자체로는 원래 객체를 참조할 수 없고, 반드시 shared_ptr로 변환해서 사용
- 이때 가리키고 있는 객체가 이미 소멸되었다면 빈 shared_ptr으로 변환
- 아닐경우 해당 객체를 가리키는 shared_ptr으로 변환된다.
출처
'C++' 카테고리의 다른 글
[C++] 형 변환 (0) | 2024.12.18 |
---|---|
[C++] 예외처리 (0) | 2024.12.18 |
[C++] 가변 길이 템플릿 (1) | 2024.12.17 |
[C++] 템플릿 (1) | 2024.12.17 |
[C++] 가상 상속 (0) | 2024.12.17 |