관리 메뉴

기억을 위한 기록들

[CPP-effective] 3-2 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자. 본문

C & CPP/Effective C++

[CPP-effective] 3-2 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자.

에드윈H 2021. 4. 1. 17:17

 

예를 들어서 뮤텍스(mutex)클래스를 만들어서 사용한다고 하자.

뮤텍스 잠금을 관리 하는 클래스를 관리하는 클래스는 기본적으로 RAII 법칙을 따라 구성합니다. 소멸 시에 그 자원을 해제하는 것입니다.

 

class Lock{
public:
    explicit Lock(Mutex* pm)  
        : mutexPtr(pm)
        {
            lock(mutexPtr); //자원을 획득합니다.
        };
        
    ~Lock() {unlock(mutexPtr); } //자원을 해제합니다.

private:
   Mutex* mutexPtr; 

};

 

이렇게 만든 클래스는 생성하고 해제가 좀 편해집니다.

 

 

Mutex m;
//...
void DoSomething()
{
      Lock m1(&m)

	  //...
}  //벗어나며 자동으로 풀립니다.

 

 

이렇게 보면 아무런 문제가 없을 것 같지만, 문제가 있습니다. 바로 Lock객체가 복사된면 어떻게 해야할까요

 

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

 

일반화 한다면 RAII 객체가 복사 될때 어떤 동작이 이루어져야 할까요??

 

1. 복사를 금지합니다.

RAII 객체가 복사되도록 하는것은 안되니 막아줘야합니다. '사본'의 의미가 없으니까요.

(private에 복사생성자와 복사대입 연산자를 선언만 해주면 됩니다.)

 

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

자원을 사용하고 있는 객체가 소멸 될때까지 그 자원을 해제 못하게 하면 됩니다. 공유 포인터의 동작처럼.

 

class Lock{
public:
    explicit Lock(Mutex* pm)  
        : mutexPtr(pm)
        {
            lock(mutexPtr); //자원을 획득합니다.
        };
        
 

private:
   shared_ptr<Mutex*> mutexPtr; //공유 포인터 사용 

};

소멸자를 없앤 이유는 공유포인터로 바뀐 mutexPtr자체의 참조 카운팅이 0이 되면 자동으로 사라지기 때문.

 

 

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

때에 따라서 자원을 원하는 대로 복사할수도 있습니다. 이때는 '자원을 다 썼을 때 각각의 사본을 확실히 해제하는 것'이 자원 관리 클래스가 필요한 유일한 명분이 되는 것이죠.

자원 관리 객체를 복사하면 그 객체가 둘러싸고 있는 자원까지 복사 되어야합니다. 즉, 깊은복사를 수행한다는 것.

 

 

4. 관리하고 있는 자원의 소유권을 옮깁니다.

흔한 경우는 아니지만, 특정한 자원에 대해 그 자원을 실제로 참조하는 RAII 객체는 딱 하나만 존재하도록 만들고 싶어서, 그 RAII객체가 복사될 떄 그 자원의 소유권을 사본 쪽으로 아예 옮겨야 할 경우도 살다보면 생깁니다.

 

정리

* RAII객체의 복사는 그 객체가 관리하는 자원의 복사 문제를 안고가기 때문에, 그 자원을 어떻게 복사하느냐에 따라 RAII객체의 복사 동작이 결정된다.

 

* RAII 클래스에 구현하는 일반적인 복사 동작은 복사를 금지하거나 참조 카운팅을 해 주는 선으로 마무리 하는 것입니다. 그 외 여러 방법도 가능은 하다.