알다시피 자원 관리 클래스, 이른바 RAII 객체의 핵심은
위의 두가지 개념이다.
class Lock{
private:
Mutex *mutexPtr;
public:
explicit Lock( Mutex *pm ) : mutexPtr(pm) // explicit : 자동 형변환 방지
{ lock(mutexPtr); }
~Lock() { unlock(mutexPtr); }
}
...
Mutex m; // 쓰고 싶은 뮤텍스 정의
...
{ // 임계영역 블록
Lock m1(&m); // 뮤텍스 잠금
...
}
위의 코드를 보면 자원을 생성하는 시점에 lock 을 통해서 초기화 하고,
반대로 소멸자에서 해당 자원에 대해서 Unlock 을 진행한다.
사용자는 단순히 RAII 객체에 자원을 넘겨서 생성하고 작업하기만 하면 블록의 끝에서 자동으로 unlock이 진행된다.
그런데 만약 m1 객체가 복사된다면 어떻게 될까?
해당 질문이 이번 장에서 묻고자 하는 핵심이다.
// RAII 복사
{
...
Lock m1(&m);
Lock m2(m1); // m1 을 복사하는 m2
...
}
위의 코드는 컴파일러가 제지하지 않지만, 실행 중에 에러가 발생한다.
( 동일 mutex에 대해서 두번의 unlock 수행 )
RAII 객체와 같은 자원 관리 객체의 복사 행위를 Handling 하는 방식에는 크게 4가지가 존재한다.
1. 복사 자체를 금지한다.
→ 사실 자원 자체도 아닌 RAII 객체 자체가 복사된다는 상황 자체가 바람직하지 않다.
상황에 따라 다르겠지만 대부분의 경우에는 설계를 재검토 하거나 방식을 다시 생각하는 것이 바람직하다.
→ 항목 6) 에서 언급한 Uncopyable 방식처럼 복사 연산 관련 함수를 private 로 선언해버리는 방식이 가장 간단하고 효율적이다.
// UnCopyable
class UnCopyable {
protected:
UnCopyable() { };
~UnCopyable() { };
private:
// 선언만 함으로서 friend를 통한 호출도 막음
UnCopyable(const UnCopyable&);
UnCopyable& operator=(const UnCopyable&);
};
...
class Lock: private Uncopyable {
public:
...
};
2. 관리하는 자원에 대해 참조 카운팅
→ std::shared_ptr 처럼 하나의 자원을 참조하는 객체의 개수에 대한 카운트를 증감시키는 방식으로 관리하는 것
그런데 문제는 shared_ptr 의 동작 방식 자체가 참조 카운트가 0이 될 때 자신이 가리키는 자원을 삭제 시키는 방식으로 구현되어있다는 것이다.
따라서 위의 예시와 같이 mutex 의 경우에는 카운트 값이 0이 되는 순간에 unlock 아닌 해제를 진행하기 때문에 부적합하다고 할 수 있다.
이것을 보완하는 방식으로 shared_ptr 에서는 삭제자(deleter) 지정을 허용한다는 점을 활용할 수 있다.
즉, 참조 카운트가 0이 되었을 때 호출할 함수를 직접 지정할 수 있다는 것이다.
// shared_ptr
void lock(std::mutex* pm)
{
pm->lock();
}
void unlock(std::mutex* pm)
{
pm->unlock();
}
class Lock {
private:
std::tr1::shared_ptr<std::mutex> mutexPtr;
public:
explicit Lock(std::mutex *pm) : mutexPtr(pm, unlock) // explicit : 자동 형변환 방지
{
lock(mutexPtr.get());
}
};
위의 예시 코드에서 하나 더 주목해야 할 점은 Lock 클래스의 소멸자를 정의하지 않았다는 것인데,
이것은 컴파일러가 자동으로 생성해주는 소멸자를 사용하겠다는 것이다.
항목 5에서 언급한 것처럼 클래스 소멸자는 자동으로 비정적 데이터 멤버의 소멸자를 호출한다.
그렇기 때문에 생성 시점에 이미 metexPtr이라는 멤버에 unlock 이라는 삭제자를 지정해줬으니,
Lock 클래스가 소멸하면서 자동으로 mutexPtr의 소멸자가 호출되면서 unlock이라는 삭제자 함수가 실행되는 것이다.
3. Deep Copy
→ 자원 관리 객체에 대한 복사 뿐만 아니라 관리되는 자원까지 함께 복사하는 방식
4. 자원 소유권 이전
→ auto_ptr 에서 보았던 방식과 마찬가지로 복사되는 새로운 대상에게로 자원의 소유권을 넘겨주고, 원본 자원 관리 객체는 NULL 처리 해버리는 방식
항목 16) new 및 delete 사용할 때는 반드시 형태를 맞추자 (0) | 2021.12.05 |
---|---|
항목 15) 자원 관리 클래스에서 관리되는 자원은 외부 접근을 차단하자 (0) | 2021.12.05 |
항목 13) 객체를 활용한 자원 관리 ( RAII 패턴 ) (0) | 2021.12.05 |
항목 12) 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2021.12.05 |
항목 11) operator= 에서 자기 대입 상황에 대한 처리를 반드시 하자 (0) | 2021.12.05 |
댓글 영역