일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- c++
- final
- C++11
- InverseKinematics
- FABIK
- FABRIK
- Kinematics
- TwoBoneIK
- Modern C++
- C++ #개발자 #Modern_C++ #스마트포인터 #Smart_Pointer #unique_ptr # shared_ptr # weak_ptr
- ImageProcessing #ComputerGraphics #ComutationalPhotography #PoissinImageEditing #Siggraph2003
- Override
- IK
- CCD
- IKSolver
- 클래스
- ComputerGraphics
- 상속
- Today
- Total
DevNote
스마트 포인터 본문
C 스타일의 프로그래밍의 주요 버그 중 하나는 메모리 누수! 누수는 대개 new를 사용하여 할당된 메모리에 대한 삭제를 호출하지 못한 경우에 발생한다. 최신 C++은 RAII[1]초기화의 원칙을 강조하는데, 이는 할당된 리소스[2]의 사용 Scope가 끝날 경우에 자동으로 리소스를 해제해주며, 예외 상황에서도 사용한 리소스의 해제를 보장하여 코드의 안정성을 확보하는 Design Pattern이다.
<auto_ptr>
#include <memory>
class A
{
...
};
int main()
{
std::auto_ptr<A> temp(new A); // auto_ptr로 생성하고 초기화
std::auto_ptr<A> ptr1(new A);
std::auto_ptr<A> ptr2(ptr1); // 소유권을 ptr1에서 ptr2로 이전
return 0;
}
메모리 할당 해제를 RAII로 지원하는 auto_ptr이 있습니다. 위의 예제를 보면 사용법은 비교적 간단한 편이다. (auto_ptr을 사용하기 위해선 memory 헤더 추가 필요, 네임 스페이스는 std) 그러나 auto_ptr은 컨테이너에 넣을 수 없는 문제가 있다. 그 이유는 일반적인 복사와는 다르게 동작하기 때문인데, 스마트 포인터 객체를 값으로 전달하면 객체복사가 일어나고, 이 때, 스마트 포인터가 가진 리소스의 복사가 일어나는 것이 아닌 리소스에 대한 소유권을 넘기는 방식이다. 따라서 A 객체에서 B로 복사하면 A가 가지고 있던 리소스가 복사되는 것이 아닌 전달이 되는 것입니다. STL의 알고리즘은 값에 의한 복사가 기본 동작이라서 컨테이너에 auto_ptr을 넣고 알고리즘을 사용하면 리소스의 소유권을 줘버리고 null을 가진 스마트 포인터고 가득 차게 될 것이다. 이를 해결하기 위해 unique_ptr이 나온 것이다.
<unique_ptr>
std::unique_ptr<A> ptr1(new A(5)); // 클래스 A 객체형 unique_ptr인 ptr1 선언 및 초기화
auto ptr2 = move(ptr1); // ptr1에서 ptr2로 소유권 이전
** unique_ptr<A> ptr3 = ptr1; // 대입 연산자를 이용한 복사는 Error발생
unique_ptr은 하나의 스마트 포인터만이 특정 개체를 소유할 수 있도록, 객체에 소유권 개념을 도입한 스마트 포인터다. 해당 객체의 소유권을 가지고 있을 때만, 소멸자가 해당 객체를 삭제할 수 있다. 또한 unique_ptr 인스턴스는 move() 멤버 함수를 통해 소유권을 이전할 수 있지만, 복사할 수는 없다(복사생성자와 대입연산을 지원하지 않으며, move 생성자 / 대입연산자만 존재). 소유권이 이전되면, 이전 unique_ptr 인스턴스는 더이상 해당 객체를 소유하지 않게 재설정된다.
C++14이후로는 make_unique()를 이용하면 unique_ptr 인스턴스를 보다 안전하게 생성할 수 있다. make_unique()는 전달받은 인수를 사용해 지정된 타입의 객체를 생성하고, 생성된 객체를 가리키는 unique_ptr을 반환한다. 다음은 class A 객체를 가리키는 a라는 unique_ptr을 make_unique() 함수를 이용해 생성하는 예제다.
<예제>
#include <iostream>
#include <memory>
using namespace std;
class A
{
private:
int num;
int size;
public:
A(int in_num, int in_size);
~A() { cout << "소멸자가 호출되었습니다." << endl; }
void Show();
};
int main()
{
unique_ptr<A> a = make_unique<A>(1, 28);
a->Show();
return 0;
}
A::A(int in_num, int in_size)
{
num = in_num;
size = in_size;
cout << "생성자가 호출되었습니다." << endl;
}
void A::Show()
{
cout << num << "번 쨰 클래스의 크기는 " << size << "입니다." << endl;
}
<결과>
위의 예제에서 A 객체를 가리키는 unique_ptr 인스턴스인 a는 일반 포인터와는 다르게 사용이 끝난 후 delete를 사용해 메모리를 해제할 필요가 없이 자동으로 사용을 마친 메모리에 대한 해제를 해주는 것을 확인 할 수 있다.
<shared_ptr>
shared_ptr은 하나의 특정 객체를 참조하는 스마트 포인터가 총 몇 개인지 참조하는 스마트 포인터다. 이 때, 참조하고 있는 스마트 포인터의 갯수를 참조 횟수(reference count)라고 한다. 참조 횟수는 특정 객체에 새로운 shared_ptr이 추가될 때마다 1씩 증가하며, 수명이 다할 떄마다 1씩 감소한다. 즉, 마지막 shared_ptr의 수명이 다하여, 참조 횟수가 0이 되면 delete를 이용해 메모리를 자동으로 해제한다.
<예제>
shared_ptr<A> ptr1(new A(1, 28)); // 클래스 A 타입의 shared_ptr인 ptr1을 선언 및 초기화
cout << ptr1.use_count() << endl; // 참조 횟수 : 1
auto ptr2(ptr1); // 복사 생성자를 이용한 초기화
cout << ptr1.use_count() << endl; // 참조 횟수 : 2
auto ptr3 = ptr1; // 대입연산자를 이용한 초기화
cout << ptr1.use_count() << endl; // 참조 횟수 : 3
위의 예제에서 사용된 use_count() 함수는 shared_ptr 객체가 현재 가리키고 있는 리소스를 참조 중인 소유자의 수를 반환해준다. 또한 make_unique()와 같은 맥락으로 make_shared() 함수는 전달받은 인수를 사용해 지정된 타입의 객체를 안전하게 생성할 수 있다.
다음의 예제는 A객체를 a라는 shared_ptr를 이용해 make_shared() 함수를 이용해 생성하는 예제이다.
<예제>
shared_ptr<A> a = make_shared<A>(1, 28);
cout << "현재 참조 횟수 : " << a.use_count() << endl;
auto b = a;
cout << "현재 참조 횟수 : " << a.use_count() << endl;
b.reset(); // shaed_ptr인 b를 해제함
cout << "현재 참조 횟수 : " << a.use_count() << endl;
<결과>
생성자가 호출되었습니다.
현재 참조 횟수 : 1
현재 참조 횟수 : 2
현재 참조 횟수 : 1
소멸자가 호출되었습니다.
위의 예제는 unique_ptr과 마찬가지로 shared_ptr의 인스턴스인 a는 별도의 delete 호출 없이 자동으로 메모리 해제가 되는 것을 보여준다. 또한, 참조 중인 b를 해제했을때, 현재 참조 중인 객체의 갯수가 갱신되는 것을 확인할 수 있다.
<weak_ptr>
weak_ptr은 하나 이상의 shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공한다. 그러나, 소유자의 수에는 포함되지 않기 때문에 shared_ptr의 순환 참조(circular reference)[3]를 방지해줄 수 있다. 즉, weak_ptr은 shared_ptr의 순환 참조를 제거하기 위해 사용된다.
출처 : https://en.cppreference.com/book/intro/smart_pointers
https://en.cppreference.com/w/cpp/memory/weak_ptr
[1] Resource Acquisition Is Initialization
[2] 대개 메모리와 핸들을 두루 일컫는 말.
[3] 서로가 상대방을 가리키는 shared_ptr이 존재하는 경우, 이 때, 참조 횟수는 절대 0이 되지 않으므로 메모리는 영원히 해제되지 않는다.
'C++ > Modern' 카테고리의 다른 글
[C++ 11] final과 override의 차이 (0) | 2020.07.17 |
---|