[C++] 스마트포인터(Smart pointer) 원리
1. 스마트포인터 사용
1.1. 생성자
#include <iostream>
using namespace std;
// C++11의 스마트포인터를 사용하려면 필요한 헤더
#include <memory>
int main()
{
// 1. explicit 생성자
std::shared_ptr<int> p1(new int); // ok
//std::shared_ptr<int> p2 = new int; // error
// 스마트 포인터 오버헤드
std::shared_ptr<int> sp1(new int);
std::shared_ptr<int> sp2 = sp1;
cout << sizeof(sp1) << endl; // 8
// (A) 관리객체가 생성되는 오버헤드
// (B) 스마트 포인터 자체의 크기가 raw pointer의 2배
}
1.2. 멤버 접근 방법
#include <iostream>
#include <memory>
using namespace std;
class Car
{
public:
void Go() { std::cout << "Go" << std::endl; }
};
int main()
{
std::shared_ptr<Car> sp1(new Car);
std::shared_ptr<Car> sp2 = sp1;
// -> 연산 : 대상체(Car)의 멤버에 접근
sp1->Go();
// . 연산 : 스마트 포인터 자체의 멤버에 접근
int n = sp1.use_count();
cout << n << endl;
Car* p = sp1.get(); // raw pointer 꺼내기
}
1.3. 삭제자 함수
#include <iostream>
#include <memory>
using namespace std;
void foo(void* p) { free(p); }
int main()
{
// 삭제자 변경
shared_ptr<int> sp1(new int);
// 생성자의 2번째 인자로 삭제자 함수 전달
// shared_ptr<void> sp2(malloc(100), foo);
// shared_ptr<void> sp2(malloc(100), free);
shared_ptr<void> sp2(malloc(100), [](void*p) { free(p); });
// 배열 할당일 경우
//shared_ptr<int> sp3(new int[10], [](int *p) {delete[] p; });
// C++17 부터 아래처럼 사용가능
shared_ptr<int[]> sp4(new int[10]);
}
1.4. 순환 참조 문제
#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct People
{
std::string name;
People(std::string n) : name(n) {}
~People() { cout << name << " 파괴" << endl; }
shared_ptr<People> bf;
};
int main()
{
shared_ptr<People> sp1(new People("kim"));
shared_ptr<People> sp2(new People("lee"));
// 메모리 누수
// 이 코드는 순환 참조 현상이 있습니다. 메모리가 해지 안됨. - 중요!!
sp1->bf = sp2;
sp2->bf = sp1;
}
- 해결방안으로 shared_ptr 대신 그냥 포인터를 사용한다면?
#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct People
{
std::string name;
People(std::string n) : name(n) {}
~People() { cout << name << " 파괴" << endl; }
//shared_ptr<People> bf;
People* bf; // raw pointer. 참조계수 증가 안함.
// 객체 파괴 조사 못함
};
int main()
{
shared_ptr<People> sp1(new People("kim"));
{
shared_ptr<People> sp2(new People("lee"));
sp1->bf = sp2.get();
sp2->bf = sp1.get();
}
// sp2가 가리키는 객체는 파괴 되었지만 raw pointer인 bf를 가지고는 확인할 방법이 없다.
if (sp1->bf != nullptr)
{
cout << sp1->bf->name << endl;
}
}
- 해결방안으로 weak_ptr을 사용하면?
#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct People
{
std::string name;
People(std::string n) : name(n) {}
~People() { cout << name << " 파괴" << endl; }
//People* bf;
weak_ptr<People> bf; // 참조계수 증가 안함, 객체 파괴 조사 가능
};
int main()
{
shared_ptr<People> sp1(new People("kim"));
{
shared_ptr<People> sp2(new People("lee"));
sp1->bf = sp2;
sp2->bf = sp1;
}
// weak 는 자신이 가리키는 곳이 유효한지 조사할 수 있다.
//if (sp1->bf != nullptr)
if ( sp1->bf.expired() )
{
cout << "객체 파괴" << endl;
}
}
1.5. shared_ptr의 관리객체
- strong cnt
- weak cnt (strong cnt + weak_ptr)
- 삭제자
- 주소지
#include <iostream>
#include <memory>
using namespace std;
class Car
{
public:
void Go() { std::cout << "Go" << std::endl; }
~Car() {std::cout << "~Car" << std::endl; }
};
int main()
{
shared_ptr<Car> sp;
weak_ptr<Car> wp;
{
shared_ptr<Car> p(new Car);
// sp = p; // 참조 계수 증가
wp = p; // 참조 계수 증가 안함
cout << p.use_count() << endl; // 2, 1
}
if (wp.expired())
{
cout << "객체 파괴" << endl;
}
else
{
//wp를 사용해서 객체 접근
//wp->Go(); // 불가능, -> 연산자 없음
// wp를 가지고 sp를 만들어서 접근, shared_ptr은 살아있는 동안 100% 안전하게 사용가능
shared_ptr<Car> sp = wp.lock();
if (sp)
sp->Go();
}
cout << "------" << endl;
}
1.6. RAII
- Resource Acquision Is Initialization
- 자원을 획득하는 것은 꼭 자원 관리 객체가 초기화 될 때 하자
#include <iostream>
#include <memory>
using namespace std;
int main()
{
int* p = new int;
//shared_ptr<int> sp1(p);
//shared_ptr<int> sp2(p); // sp1과는 다른 별개의 관리객체 사용
// 절대 이렇게 사용하면 안됩니다.
// 자원을 획득하는 것은 꼭! 자원 관리 객체가 초기화 될 때 해야 한다.
// Resource Acquision Is Initialization
// RAII idioms
shared_ptr<int> sp1(new int);
shared_ptr<int> sp2(new int);
}
참고
codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.
코드누리
This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄.
댓글남기기