본문 바로가기
Object Oriented Programming(C++)/Effective C++

Effective C++ | 항목 14 자원 관리 클래스의 복사 동작을 고찰하자

by continue96 2022. 1. 24.

항목 14 자원 관리 클래스의 복사 동작을 고찰하자

14.1 RAII 클래스의 복사 동작

 자원 관리 클래스를 스스로 만들어야 하는 경우가 있다. 예를 들어, Mutex 타입의 뮤텍스 객체로 C API를 사용하고 있다고 가정해보자. RAII 법칙에 따라 이전에 걸어 놓은 뮤텍스 잠금을 풀어주는 Lock 클래스는 다음과 같이 정의할 수 있다.

void lock(Mutex* pm); /* pm이 가리키는 뮤텍스의 잠금을 설정하는 C API 함수입니다. */
void unlock(Mutex* pm); /* pm이 가리키는 뮤텍스의 잠금을 해제하는 C API 함수입니다. */

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

 

 사용자는 Lock을 사용할 때 RAII 방식에 맞추어 쓰면 된다. 그런데 Lock 객체가 복사된다면 어떻게 해야 할까? 사용자는 RAII 객체가 복사될 때 어떤 동작이 이루어질 것인지에 대해 다음 선택지 중 하나를 골라야 한다.

Mutex m; /* 뮤텍스를 정의합니다. */
Lock ml1(&m); /* 뮤텍스 m에 잠금을 설정합니다. */
Lock ml2(ml1); /* 다음 선택지 중에서 복사 동작을 처리합니다. */

 

 14.1.1 복사를 금지한다

 RAII 객체가 복사되는 것이 말이 안 되는 경우가 꽤 많다. 위의 Lock 클래스도 스레드 동기화 객체의 사본이라는 것이 거의 의미가 없으므로 이러한 부류에 속한다. 복사하면 안 되는 RAII 클래스에 대해서는 반드시 복사가 되지 않도록 해야 한다. 이 방법은 항목 6을 참고한다.

 

 14.1.2 관리하고 있는 자원을 레퍼런스 카운팅한다.

 자원을 사용하고 있는 마지막 객체가 소멸될 때까지 자원을 해제하지 않는 것이 바람직한 경우가 있다. 이러한 경우, 해당 자원을 참조하는 객체의 개수를 세는 식으로 RAII 복사 동작을 만들어야 한다.

 Lock이 참조 카운팅 방식의 복사 동작을 하도록 만들려면 shared_ptr를 데이터 멤버로 넣고 삭제자(deleter)를 지정한다. 삭제자란 shared_ptr가 보유하는 참조 카운트가 0이 되었을 때 호출될 함수 혹은 함수 객체를 일컫는다. 삭제자는 shared_ptr의 두 번째 매개 변수로 넣어줄 수 있다.

class Lock {
public:
	explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) { /* 삭제자로 unlock 함수를 사용합니다. */
		lock(mutexPtr.get());
	}
	/* 소멸자는 선언하지 않습니다. */
private:
	std::shared_ptr<Mutex> mutexPtr; /* 일반 포인터 대신 shared_ptr를 사용합니다. */
};

 

 이 예제에서 Lock 클래스는 소멸자를 명시적으로 선언하지 않았다. 컴파일러가 생성한 소멸자를 통해 호출된 mutexPtr의 소멸자는 참조 카운트가 0이 될 때 shared_ptr의 삭제자를 자동으로 호출한다.

 

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

 때에 따라서 자원을 원하는 대로 복사해야할 수도 있다. 자원 관리 객체를 복사하면 그 객체가 둘러싸고 있는 자원까지 깊은 복사(deep copy)를 수행해야 한다.

 

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

 흔한 경우는 아니지만, 어떤 특정한 자원을 참조하는 RAII 객체가 딱 하나만 있도록 하기 위해 RAII 객체가 복사될 때 그 자원의 소유권을 사본 쪽으로 아예 옮겨야 할 경우도 있다. 이러한 스타일의 복사는 항목 13에서 본 적 있는 auto_ptr의 복사 동작이다.

 

NOTE
① RAII 객체의 복사는 그 객체가 관리하는 자원의 복사 문제를 안고 가기 때문에 그 자원을 어떻게 복사하는지에 따라 RAII 객체의 복사 동작이 결정된다.
② RAII 클래스에서 구현하는 일반적인 복사 동작은 복사를 금지하거나 참조 카운팅하는 것이다.

댓글