본문 바로가기

C, C++/Effective C++

3장 리소스 관리

Item13: 객체를 사용해 자원을 관리하라.

힙에 자원을 할당하고 해제하지 않으면 메모리 누수가 생긴다.

이는 클라이언트가 까먹을 수도 있고, 예외 발생 시에 예상과 다르게 해제가 되지 않을 수 있기 때문에 자원 해제를 보장해주면 좋다.

 

이를 위해 RAII(Resource Acquisition Is Initialization)의 개념을 사용한다.

unique_ptr(책에서는 auto_ptr) 또는 shared_ptr 합쳐서 스마트 포인터를 사용해서 관리하는 것이 편하다.

 

책에서는 auto_ptr이 복사 시에 원본이 nullptr이 되기 때문에 shared_ptr을 사용하는 것이 좋다고 했지만, unique_ptr로 바뀌면서 어느정도 문제점이 해결된 것으로 보인다.

 

따라서 unique_ptr과 shared_ptr의 용도에 따라 사용하는 것이 좋을 것 같다.

unique_ptr은 해당 자원의 소유권을 단 하나만 가질 수 있다. 만약에 소유권을 주고 싶다면 이동(move)연산을 통해서 이전한다.

shared_ptr은 해당 자원을 몇개가 참조하고 있는지를 reference count를 통해 확인한다. 만약 아무도 참조하지 않는다면 그 때 해당 자원을 delete한다.

 

결론: 이러한 스마트 포인터(RAII 패턴을 곁들인)를 사용해 자원을 관리하자. -> 팩토리 함수가 스마트 포인터를 반환

 

++

스마트 포인터 생성 시 make_unique, make_shared를 사용하는 것이 최적화에 유 


Item14: 자원 관리 클래스에서 복사 동작을 신중하게 고려하라.

item13에서 사용한 스마트 포인터의 개념은 힙 기반 자원을 다루는 방법이다.

힙 기반이 아닌 자원을 관리할 때는, 직접 자원 관리 클래스를 만들어야 한다.

 

class Lock {
public:
    explicit Lock(Mutex *pm)   // 뮤텍스로 `shared_ptr` 초기화
    : mutexPtr(pm, unlock)     // `shared_ptr`이 `pm`을 가리키고, unlock 함수를 deleter로 사용
    { 
        lock(mutexPtr.get());  // `get()`을 사용하여 원시 포인터에 접근 
    }

private:
    shared_ptr<Mutex> mutexPtr;  // 기존 raw pointer 대신 `shared_ptr` 사용
};

 

위의 코드가 이번 아이템 결론이다. 그냥 사용하는(스택) 뮤텍스에 RAII 기반으로 Lock을 사용하는 것이다.

shared_ptr의 deleter를 지정하여 shared_ptr인 mutexPtr을 아무도 참조하지 않을 때 lock의 unlock 함수를 실행하도록 만든다.

 

또한 이 아이템에서 RAII 클래스의 복사를 만들기 위해 Uncopyable을 만들어 상속하는 코드가 있는데 현대 C++에서는 생성자에 =delete를 사용해 막을 수 있다.


Item15: 자원 관리 클래스에서 원시 자원(raw resource)에 대한 접근을 제공하라.

이상적으로는 자원 관리 클래스를 통해서만 자원과 상호작용 해야 하지만, 현실적으로는 어렵다.

자원 관리 클래스를 우회하여 직접 원시 자원을 다뤄야 하고, 이를 위해 변환이 필요하다.

 

자원 관리 클래스의 대표적인 예인 스마트 포인터를 통해서 본다.

 

명시적 변환(explicit)

    std::unique_ptr<Impl> pImpl; // 실제 구현체의 포인터

일 때

pImpl.get();

을 통해서 원시 자원을 받는다.

 

암시적 변환

pImpl->func();

위와 같이 -> 연산자를 통해 원시자원에 접근해 해당 자원의 기능을 활용할 수 있다.

 

만약에 명시적 변환만을 원한다면 explicit을 사용

 

명시, 암시 변환은 설계에 따라 유용성을 결정

 


Item16: new와 delete를 사용할 때 동일한 형태를 사용하라 .

new 에는 delete, new[]에는 delete[] 쌍을 맞추지 않으면 오류, 메모리 누수가 발생한다.


Item17: new로 생성한 객체를 스마트 포인터에 저장할 때 독립된 문장에서 처리하라.

int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

우선순위를 반환하는 함수와, 우선순위에 따라 동적으로 할당하는 Widget을 처리하는 함수

 

processWidget(new Widget, priority());

이 때 생성자는 명시적인 포인터를 스마트 포인터로 암시적 변환할 수 없기 때문에 컴파일 오류가 발생한다.

 

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());

만약에 이렇게 선언하더라도,1.new Widget 실행 2. priority()호출 3. shared_ptr 생성자 호출

이 세가지의 순서가 어떻게 될지 보장할 수 없고, 1 -> 2 -> 3 순서로 실행되면서, priroity() 호출 중에 오류가 발생한다면 메모리 누수가 발생한다.

 

책의 해결법

std::tr1::shared_ptr<Widget> pw(new Widget);  // ✅ 스마트 포인터에 안전하게 저장
processWidget(pw, priority());  // ✅ 이제 예외가 발생해도 메모리 누수 없음

 

모던 C++

processWidget(std::make_shared<Widget>(), priority());  // ✅ 안전 + 최적화

'C, C++ > Effective C++' 카테고리의 다른 글

5장 구현  (0) 2025.03.25
4장 설계와 선언  (0) 2025.03.21
2장 생성자, 소멸자 그리고 할당 연산자 (2)  (0) 2025.03.17
2장 생성자, 소멸자 그리고 할당 연산자 (1)  (0) 2025.03.16
1장 C++과 친해지기 (2)  (0) 2025.03.15