[Effective C++] 항목 13 : 자원 관리에는 객체가 그만!

수민이슈·2023년 3월 20일
0

Effective C++

목록 보기
13/30
post-thumbnail

스콧 마이어스의 Effective C++을 읽고 개인 공부 목적으로 요약 작성한 글입니다!

💡 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하는 RAII 객체를 사용하자!
💡 일반적으로 널리 쓰이는 RAII 클래스는 tr1::shared_ptrauto_ptr 이다
💡 이들 중, shared_ptr가 복사시의 동작이 직관적이기 때문에 더 좋다.
auto_ptr는 복사되는 객체 (원본 객체)를 null로 만든다.


🖊️ 자원(resource)

사용을 일단 마치고 난 후에는 시스템에 돌려줘야 하는 모든 것.

ex) 동적 할당한 메모리, 파일 서술자, mutex lock, font, brush, DB 연결, 네트워크 소켓 등

가져와서 다 썼으면 해제해야 한다


🖊️ 객체를 삭제하는 곳

예시 상황

class Investment { ... };

Investment* createInvestment();		// Investment 계통에 속한 클래스의 객체를 동적할당하고 그 포인터를 반환

void f() {
	Investment* pInv = createInvestment();
    ...
    delete pInv;
};

createInvestment()
: 최상위 클래스인 Investment 계통에 속한 클래스의 객체를 동적할당하고, 그 포인터를 반환해주는 함수

createInvestment() 함수를 통해 얻은 객체를 다 사용했으면 이 함수의 호출자에서 삭제해줘야 한다.
-> f()

문제 발생 가능성

항상 delete 문이 실행될거라는 믿음은 금물!

  1. ... 부분에서 return문이 있을 경우
  2. 하나의 루프 안에 createInvestment()와 delete가 같이 있는 상황에서 continue로 루프를 빠져나올 경우
  3. ... 부분 중 어떤 문장에서 예외가 나올 경우

이 경우, delete 문이 실행되지 않는다.
결과는?
...
Investment 객체를 담고 있는 메모리가 누출되고, 객체가 갖고 있던 자원까지 누출된다...


🖊️ 자원 관리에 객체를 사용하자

자원을 객체에 넣고, 그 자원 해제를 소멸자가 맡도록 하자

그리고 그 소멸자는 실행 제어가 f를 떠날 때 호출되도록 하자!

자원을 객체에 넣어서, C++이 자동으로 호출해주는 소멸자에 의해 자원을 자동으로 해제하도록 하자.

이 방법의 특징

자원을 획득한 후에 자원 관리 객체에게 넘긴다.

RAII (Resource Acquisition Is Initialization) : 자원 획득 즉 초기화

자원 획득과 자원 관리 객체의 초기화가 한 문장에서 이루어진다.
RAII는 자원 관리에 객체를 사용하는 아이디어를 의미한다.

자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 한다.

소멸자는 어떤 객체가 소멸될 때 자동적으로 호출된다.
실행 제어가 무슨 이유로 블록을 떠나는지에 관계없이, 자원 해제가 이루어진다.


🖊️ 스마트 포인터

가리키고 있는 대상에 대해 소멸자가 자동으로 delete를 호출해주도록 설계된 클래스.

하나의 블록, 함수로부터 실행 제어가 빠져나올 때 자원이 해제되도록 해준다.

auto_ptr

void f() {
	std::auto_ptr<Investment> pInv(createInvestment());
    ...
    std::auto_ptr<Investment> pInv2(pInv);
    pInv = pInv2;
}

이렇다고 해주자

auto_ptr의 문제점

어떤 객체를 가리키는 auto_ptr의 개수가 둘 이상이면 안된다.
자신이 소멸될 때 자신이 가리키고 있는 대상에 대해 자동으로 delete해주기 때문이다.

따라서

auto_ptr 객체를 복사하면 원본 객체는 null이 된다.
복사하는 객체만이 그 자원의 유일한 소유권을 갖는다.

위 상황에서,
pInv2를 pInv1을 통해 복사 생성했다
따라서 pInv2는 현재 createInvestment()에서 반환된 객체를 가리키고, pInv은 null이다.
pInv = pInv2를 통해, 다시 pInv은 createInvestment()에서 반환된 객체를 가리키고, pInv2는 null이다.

어때

따라서

모든 자원에 대한 관리 객체로서 auto_ptr을 쓰는 것은 부적절하다.
STL 컨테이너들은 정상적인 복사 동작이 필요하거등..

shared_ptr

참조 카운팅 방식 스마트 포인터 (RCSP : reference-counting smart pointer)

특정한 어떤 자원을 가리키는 외부 객체의 개수를 유지하고 있다가, 그 개수가 0이 되면 해당 자원을 삭제한다.

단, 다른 두 객체가 서로를 가리키고 있는 상황에서는 ,, 어쩔 수 없다

void f() {
	...
    std::tr1::shared_ptr<Investment> pInv(createInvestment());
    
    std::tr1::shared_ptr<Investment> pInv2(pInv);
    
    pInv = pInv2;
};

이러한 상황에서, pInv와 pInv2 모두 createInvestment()에서 반환된 객체를 가리킨다.
복사가 정상적으로 동작하기 때문에 STL 컨테이너의 환경에서도 사용할 수 있다.


🖊️ 스마트 포인터의 delete

auto_ptr와 shared_ptr는 delete [] 연산자를 사용하지 않는다.

delete 연산자를 사용한다.

따라서, 동적 할당한 배열에 대해 auto_ptr나 shared_ptr를 사용하면 안된다.

컴파일 에러가 안나니까 조심해야 한다..

예시

std::auto_ptr<std::string> aps(new std::string[10]);

std::tr1::shared_ptr<int> spi(new int[1024]);

둘다 잘못된 delete가 사용되니까 이렇게 하면 안된다.
근데 앞서 말했듯, 컴파일에러가 나지는 않으니까 주의해야 한다!!

동적으로 할당한 배열을 꼭,, 쓰고 싶다면
boost::scoped_array나 boost::shared_array를 사용하셈


😊 느낀점


c++의 필수품,, 스마트 포인터
좀 더 정확히 알게 된 느낌?
나 저번에 이 부분,, 메모리 누수 났던 기억이 있어서 조금 더 열심히 봤다..
동적할당한 객체에 대해서는 꼭,, 스마트포인터를 쓰도록 해욥

0개의 댓글