스마트 포인터
스마트 포인터
- 임의의 객체가 다른 타입의 포인터 처럼 사용되는 것
- * 연산자와 -> 연산자를 재정의 해서 포인터 처럼 보이게 한다
- * 연산자는 참조 리턴을 해야 한다.
장점
- 진짜 포인터가 아니라 객체 이다.
- 생성/복사/대입/소멸의 모든 과정을 사용자가 제어할 수 있다.
- 대표적인 활용이 소멸자에서의 자동 삭제 기능
- garbage collector 와 유사한 기능
```cpp
#include
- garbage collector 와 유사한 기능
```cpp
#include
class Car { public: void Go() { std::cout « “Car Go” « std::endl; } ~Car() { std::cout « “~Car” « std::endl; } };
// 아래 클래스가 핵심이다. class Ptr { Car* obj; public: Ptr(Car* p = nullptr) :obj(p) {}
~Ptr() {
delete obj;
}
Car* operator->() {
return obj;
}
// 반드시 참조리턴 해야 한다.
// Car 리턴 은 임시객체 이다: 다른 Car 객체가 만들어 진다.
Car& operator*() {
return *obj;
} };
int main() { Ptr p = new Car; // Ptr p(new Car) 로 생각해 보자 p->Go(); // (p.operator->()) G0() 의 모양 이지만 // (p.operator->())->Go() 로 해석해 준다. (*p).Go(); // 진짜 포인터 처럼 보이려면 *p.Go() 도 되어야 한다. }
1. template 으로 만들어야 한다.
2. 얕은 복사 문제를 해결해야 한다.
- 깊은 복사: 스마트 포인터 만들때는 거의 사용하지 않는다
- 참조 계수: 스마트 포인터 만들때 가장 널리 사용되는 방식
- 소유권 이전: ???
- 복사 금지: 요즘 뜨고 있는 방식
```cpp
template<typename T>
class Ptr {
T* obj;
public:
Ptr(T* p = nullptr) : obj(p) {}
~Ptr() {
delete obj;
}
T* operator->() {
return obj;
}
T& operator*() {
return *obj;
}
};
int main() {
Ptr<int> p1 = new int;
Ptr<int> p2 = p1;
}
참조 계수 방식
template<typename T>
class Ptr {
T* obj;
int* ref;
public:
Ptr(T* p = nullptr) : obj(p) {
ref = new int(1);
}
// 참조 계수 방식의 복사 생성자
Ptr(const Ptr& p) {
// 얕은복사 이후
obj = p.obj;
ref = p.ref;
// 참조 계수 증가
++(*ref);
}
~Ptr() {
if (--(*ref) == 0) {
delete obj;
delete ref;
}
}
T* operator->() {
return obj;
}
T& operator*() {
return *obj;
}
};
int main() {
Ptr<int> p1 = new int;
Ptr<int> p2 = p1;
}
class Animal {};
class Dog : public Animal {};
template<typename T>
class Ptr {
T* obj;
int* ref;
public:
// 변환 생성자를 막기 위해 explicit 로 선언 한다.
explicit Ptr(T* p = nullptr) : obj(p) {
ref = new int(1);
}
// 일반 복사생성자
Ptr(const Ptr& p) {
obj = p.obj;
ref = p.ref;
++(*ref);
}
// 일반화 된 복사 생성자
// 부모 포인터로 자식 포인터를 가르키게 해야 한다.
template<typename U>
Ptr(const Ptr<U>& p) {
obj = p.obj;
ref = p.ref;
++(*ref);
}
template<typename> friend class Ptr;
// 그 외에 대입연산(=), ==, != 연산자가 template 버전으로 있어야 한다.
~Ptr() {
if (--(*ref) == 0) {
delete obj;
delete ref;
}
}
T* operator->() {
return obj;
}
T& operator*() {
return *obj;
}
};
int main() {
Ptr<Dog> p1(new Dog); // explicit 생성자이기 때문에 () 초기화만 가능하다.
Ptr<Animal> p2 = p1; // 다형성: 부모 포인터로 자식을 가르킬 수 있어야 한다.
}
이미 c++ 표준에 참조계수 스마트 포인터가 있다
#include <memory>
int main() {
std::shared_ptr<int> p1(new int);
std::shared_ptr<int> p2 = p1; // 이 순간 참조계수가 증가 한다.
}
참조 계수를 객체 안에 포함 시키자
- 객체 생성 시 참조계수 증가
- 객체 소멸 시 참조계수 감소
- 객체 사용 후 참조계수 감소
```cpp
#include
class Car { // Car 의 고유 멤버들 int mCount; // 참조 계수 public: Car() : mCount(0) {} ~Car() { std::cout « “~Car” « std::endl; }
void incStrong() {
++mCount;
}
void decStrong() {
if (--mCount == 0) {
delete this;
}
} };
int main() { Car* p1 = new Car; p1->incStrong(); Car* p2 = p1; p2->incStrong(); p2->decStrong(); p1->decStrong(); } // ~Car
---
객체 안의 참조 계수를 자동으로 관리 해 주는 스마트 포인터
```cpp
#include <iostream>
class Car {
// Car 의 고유 멤버들
int mCount; // 참조 계수
public:
Car() : mCount(0) {}
~Car() {
std::cout << "~Car" << std::endl;
}
void incStrong() {
++mCount;
}
void decStrong() {
if (--mCount == 0) {
delete this;
}
}
};
template<typename T>
class sp {
T* obj;
public:
sp(T* p = nullptr) :obj(p) {
if (obj) {
obj->incStrong();
}
}
sp(const sp& p) :obj(p.obj) {
if (obj) {
obj->incStrong();
}
}
~sp() {
if (obj) {
obj->decStrong();
}
}
// 스마트 포인터의 기본 * 와 ->
T* operator->() {
return obj;
}
T& operator*() {
return *obj;
}
};
// sp 를 사용하려면 반드시 incStrong() 과 decStrong() 이 있어야 한다.
// sp 를 사용하기 위해 _RefBase_ 의 파생 클래스로 만들자
class Truck/* :public RefBase*/ {
public:
~Truck() {
std::cout << "~Truck" << std::endl;
}
};
int main() {
sp<Car> p1 = new Car;
sp<Car> p2 = p1;
sp<Truck> p3 = new Truck;
} // ~Car
객체 관리의 어려움
#include <vector>
#include <list>
class Car {};
std::vector<Car*> v;
std::list<Car*> l;
void foo() {
Car* p = new Car;
p->incStrong();
v.push_back(p);
p->incStrong();
l.push_back(p);
p->incStrong();
// 여기서는 더이상 p 가 필요하지 않다.
// 삭제해도 될까?
delete p;
p->decStrong();
}
int main() {
foo();
}
스마트 포인터가 참조계수를 관리하는 2가지 방식
- 참조계수를 위한 별도의 메모리 할당
- std::shared_ptr<>: c++ 표준, 모든 타입에 대해 사용 가능
- 객체 내부에 참조계수를 포함
- c++ 표준이 아님
- 각 라이브러리가 자체적으로 제공하는 기술
- 안드로이드
- RefBase
- sp
```cpp
#include
template
// 부모 포인터인 상태로 delete 하면 부모의 소멸자만 호출이 된다.
// 자식 소멸자를 호출 되게 하려면 소멸자 자체가 가상함수이어야 한다.
// 결국 모든 부모의 소멸자는 가상함수 이어야 한다.
template
class Truck : public RefBase
int main() {
sp
[RefBase.h]: https://android.googlesource.com/platform/system/core/+/refs/heads/master/libutils/include/utils/RefBase.h
> [RefBase.h][]
---
```cpp
#include <iostream>
template<typename T>
class sp {
T* obj;
public:
sp(T* p = nullptr) : obj(p) {
if (obj) {
obj->incStrong();
}
}
sp(const T& p) :obj(p.obj) {
if (obj) {
obj->incStrong();
}
}
~sp() {
if(obj) {
obj->decStrong();
}
}
T* operator->() {
return obj;
}
T& operator*() {
return *obj;
}
};
// 참조계수를 조작하는 함수는 반드시 상수함수 이어야 한다.
template<typename T>
class RefBase {
mutable int mCount;
public:
inline RefBase() : mCount(0) {}
inline virtual ~RefBase() {}
inline void incStrong() const {
++mCount;
}
inline void decStrong() const {
// this: const RefBase* 이다.
// static_cast 는 상수를 제거할 수 없다.
// RefBase* => T* : ok.
// const RefBase* => const T* : ok.
// const RefBase* => T* : error.
if (--mCount == 0) {
delete static_cast<const T*>(this);
// delete static_cast<const_cast<RefBase*>>(this);
}
}
};
class Truck : public RefBase<Truck> {
public:
~Truck() {
std::cout << "~Truck" << std::endl;
}
};
int main() {
sp<Truck> p = new Truck;
const Truck t;
t.incStrong(); // 상수 객체도 참조계수 관리는 할 수 있어야 한다.
} // ~Truck
// ~Truck
댓글남기기