KoreanFoodie's Study

Effective C++ | 항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 본문

Tutorials/C++ : Advanced

Effective C++ | 항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자

GoldGiver 2022. 10. 25. 16:09

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자

핵심 :

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

 

우리가 직접 RAII 클래스를 만들어야 하는 다음과 같은 상황이 생겼다고 가정하자.

class Lock
{
  public:
    explicit Lock(Mutex *pm)
    : mutexPtr(pm)
    { lock(mutextPtr); } // 자원 획득
    
    ~Lock() { unlock(mutextPtr); } // 자원 해제
    
  private:
    Mutex *mutexPtr;
};

...

// 사용하고 싶은 뮤텍스를 정의한다
Mutex m;
...
// 임계 영역을 정하기 위해 블록을 만든다
{
  // 뮤텍스에 잠금을 건다
  Lock m1(&m);
  
  // 임계 영역에서 할 연산을 수행한다
  ...
  
  // 블록의 끝에서 뮤텍스의 잠금이 자동으로 풀린다
}

위의 예시는 잘 동작할 것 같지만, 아래처럼 Lock 객체가 복사되는 경우에는 어떻게 작동할까? 

Lock ml1(&m);
// ml1 을 ml2 로 복사. 어떻게 되어야 할까?
Lock ml2(ml1);

아마 대부분의 경우, 다음의 선택지들 중 하나를 골라잡을 것이다.

 

복사를 금지한다

실제로, 복사하면 안되는 RAII 클래스의 경우, 복사가 되지 않도록 막아야 한다. 예시는 다음과 같다. (항목 6 참고)

// 복사를 금지한다
class Lock: private UnCopyable
{
   ...
}

 

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

shared_ptr 처럼 위의 Lock 클래스를 활용하면 어떨까? mutexPtr를 shared_ptr 로 선언하는 것이다! 다만, 참조하고 있는 포인터가 없을 때는 뮤텍스를 삭제하는 것이 아니라, 잠금 해제만 하도록 만들고 싶다. unique_ptr 는 해당 기능이 지원되지 않지만, shared_ptr 에서는 생성자의 두 번째 매개변수를 넣어 '삭제자' 함수를 지정해 줄 수 있다.

class Lock
{
  public:
    // 초기화 시, 삭제자로 unlock 함수를 사용한다
    explicit Lock(Mutex *pm)
    : mutexPtr(pm, unlock)
    {
      lock(mutexPtr.get());
    }
    
  private:
    // 원시 포인터 대신 shared_ptr 을 사용한다
    std::shared_ptr<Mutex> mutexPtr;
}

위의 코드에서는 소멸자 선언이 되어 있지 않은데, 그 이유는 Lock 에서 지정한 것처럼, mutexPtr 의 참조 카운트가 0이 될 때 삭제자(여기서는 unlock 함수) 가 호출될 것이기 때문이다.

 

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

'깊은 복사(deep copy)' 를 수행한다

 

관리하고 있는 자원의 소유권을 옮긴다

unique_ptr 처럼 std::move 를 사용해 소유권을 완전히 이전한다.

Comments