[Effective C++] 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자

C++/Effective C++

2020. 8. 3. 21:03

1. 모든 자원이 힙에서 생기지 않는다.

힙에 생기지 않는 자원은 스마트 포인터로 처리해 주기엔 맞지 않다는 것이 일반적인 견해이다. 항상 그런 것은 아니지만, 자원 관리 클래스를 스스로 만들어야 할 필요를 느끼는 경우가 이러한 경우이다.

 

class Lock{
public:
    explicit Lock(Mutex* pm): mutexPtr(pm)
    { lock(mutexPtr);}
    
    ~Lock(){unlock(mutexPtr);}
private:
	Mutex* mutexPtr;
};

 만약 Lock 객체가 복사 되는 경우는 어떻게 될까?

 

Lock ml1 (&m);
Lock ml2 (ml1);

2. RAII 객체가 복사될 때 이루어져야 하는 동작

1. 복사를 금지한다.

 

위의 Lock과 같은 클래스들은 객체가 복사되도록 놔두는 것 자체가 말이 안되는 경우이다. 어떤 스레드 동기화 객체에 대한 '사본' 이라는 게 실제로 의미가 없기 때문이다.

 

2. 관리하고 있는 자원에 대해 참조 카운팅을 수행한다.

 

shared_ptr을 이용함으로서 쉽게 해결할 수 있다. 즉, 위의 mutexPtr을 Mutex*에서 shared_ptr<Mutex>로 바꾸는 것. 하지만, shared_ptr은 참조 카운팅이 0이 되는 순간 가리키고 있던 대상을 삭제해 버리도록 기본 동작이 만들어져 있어서, Mutex의 경우 해제만 하면되는 것을 삭제까지하는 상황이 생긴다. 다행히 shared_ptr의 삭제자(deleter) 지정을 통해 참조 카운팅이 0이 되는 순간 호출할 함수를 지정할 수 있다.

 

class Lock{
public:
     explicit Lock (Mutex* pm) : mutexPtr(pm,unlock)
     {
     	lock(mutexPtr.get());
     }
private:
	std::share_ptr<Mutex> mutexPtr;
};

전의 코드와 확연하게 다른 부분이, 소멸자가 존재하지 않다는 것이다. 이유의 즉슨, mutexPtr가 비정적 멤버 데이터이기 때문에 소멸자를 자동으로 호출하게 되어있다.

 

3. 관리하고 있는 자원을 진짜로 복사한다.

4. 관리하고 있는 자원의 소유권을 옮긴다. (unique_ptr 이용)

 

이전 포스팅에서 C++11에 deleter 기능이 추가된건줄 알았는데 예전부터 있었나보네요!

 

이것만은 잊지 말자!

  • RAII 객체의 복사는 그 객체가 관리하는 자원의 복사 문제를 안고 가기 때문에, 그 자원을 어떻게 복사하느냐에 따라 RAII 객체의 복사 동작이 결정됩니다.
  • RAII 클래스에 구현하는 일반적인 복사 동작은 복사를 금지하거나 참조 카운팅을 해 주는 선으로 마무리하는 것입니다. 하지만 이 외의 방법들도 가능하니 참고해 둡시다.