자원관리 클래스의 복사 동작에 대해(effective c++ 14)

2015. 8. 12. 01:57프로그래밍/Effective C++

728x90
728x90

상황

RAII(Resource Acquisition is Initialization), 자원의 획득으로 초기화된 자원 관리 클래스의 객체가 

복사 생성자를 통해 복사되는 상황.


문제
자원 관리 클래스 객체는 생성 시에 자원을 획득하고, 소멸 시에 해당 자원을 해제하는 용도로 쓰인다.
그러나, 해당 객체가 두 개 이상 존재하게 되면 자원을 두 번 이상 해제하는 중복 해제 문제가 생기게 된다.
또한, 두 스레드가 동시에 소유할 수 없는 동기화 객체인 뮤텍스(Mutex, Mutual Exclusion)와 
같은 경우에도 자원 관리 객체가 두 개 이상 존재할 이유는 없다.

해결1. 복사 자체를 금하기

상황

RAII 객체가 복사되도록 놔두는 것 자체가 말이 안 되는 경우.

예를 들어, 스레드 동기화 객체의 경우 사본은 의미가 없다.


방법

복사 생성자를 private으로 정의해 복사 생성자 호출을 막는다.

대입 연산자를 private으로 정의해 복사가 이뤄지지 않도록 한다.

복사 방지 처리가 된 클래스를 기반 클래스(base class)로서 상속한다.


해결2. 참조 카운팅 수행
상황
하나의 자원을 두 개 이상의 RAII 객체가 참조해야 하는 경우.
자원을 참조하던 마지막 객체가 소멸하기 전까지 해당 자원을 해제해서는 안 되는 경우.

문제
하나의 자원을 참조하던 두 개 이상의 객체들을 연달아 해제하면 중복 해제 문제가 발생한다.
자원을 참조하는 객체가 하나라도 존재하는 경우, 해당 자원을 해제하면 
해당 객체가 존재하지 않는 자원을 참조하는 문제가 발생할 수 있다.

방법
자원을 참조하는 객체의 개수에 대해 카운트(count)를 증가시키는 RAII 객체의 복사 동작을 정의한다.
이러한 동작으로 구현된 스마트 포인터를 참조 카운팅 방식 스마트 포인터(RCSP(Reference-Counting Smart Pointer)라고 
부른다. 대표적인 RCSP로는 shared_ptr이 있다.

해결3. 자원을 깊은 복사 한다.
상황
자원을 원하는 대로 복사해야 하는 경우.
복사된 자원을 다 쓰고 나면 각 사본을 담당한 RAII 객체가 관리 자원을 개별적으로 해제해야 하는 경우.

문제
기본적으로 제공되는 복사 생성자를 통한 얕은 복사(shallow copy)로는 자원을 개별적으로 참조할 수 없다.

방법
RAII를 비롯한 자원 관리 객체를 복사하면 해당 객체 뿐만 아니라 해당 객체가 관리하는 자원까지 
힙(heap) 메모리 공간으로 깊은 복사(deep copy)를 수행한다.

해결4. 자원 소유권을 이전
상황
특정한 자원에 대해 해당 자원을 참조하는 RAII 객체를 단 하나만 존재하도록 해야 하는 경우.
단 하나만 존재해야 하는 RAII 객체가 복사될 때 해당 자원의 소유권을 사본 쪽으로 이전해야 하는 경우.

문제
기본적으로 제공되는 복사 생성자를 통한 얕은 복사로는 해당 자원을 두 개 이상의 객체가 가리키게 된다.

방법
auto_ptr의 복사 동작처럼, 사본 객체만이 유일한 소유권(ownership)을 가지도록 
복사 생성자와 대입 연산자를 오버로딩해 복사되는 원본 객체를 null을 가리키게 만든다.

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

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


728x90
반응형