자원 - 사용을 마치고 난 후에는 시스템에 돌려주어야 하는 모든 것을 말함.

Item 13 자원관리에는 객체가 그만!

펙토리함수 : 어떤 객체에대한 포인터를 손에 얻는 용도

어떤 함수안에서 펙토리함수를 사용하여 객체에 대한 포인터를 얻은뒤, 그 객체를 사용하다가 문제가 발생하면 예외가 발생할수 있음. 예외가 발생하게 되면 delete문을 건너뛸수도 있다. 그럴 경우 메모리 릭이 생기게 됨.

펙토리함수로부터 얻어낸 자원이 항상 해제되도록 만들 방법은 자원은 객체에 넣고 그 자원 해제는 소멸자가 맡도록 하고, 함수를 떠날때 호출되도록 만들게 한다. 스마트포인터!!

1
2
3
4
5
6
자원을 획득한 후에 자원관리 객체에게 넘긴다.
'RAII(Resource Acqusition is Initializtion)' 자원획득 즉 초기화라는 이름의 아이디어. 자원을 획득하고 나서 바로 자원관리 객체에 넘겨준다.

둘째. 자원관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 한다.
unique_ptr을 사용할수 없는 경우에는, unique_ptr는 오직 하나의 참조만 가능함. 사용이 불가능해보일때에는 RCSP(Reference counting smart pointer) 참조 카운팅 방식 스마트포인터 = Shared_ptr을 사용하는것도 괜찮다.
RCSP는 특정 자원에대해 외부 객체의 갯수를 유지하고 있다가, 그 갯수가 0이 되면 해당 자원을 자동으로 삭제한다.

Item 14 자원 관리 클래스의 복사 동작에 진지하게 고찰하자.

RAII 객체가 복사될때 어떤 일이 일어나야되는가? 해당자원이 고유적인데, 객체가 복사된다면 소멸자 호출시에 그 객체는 어떻게 될까?

선택 가능한 복사동작에 대한 조건들

  1. 복사를 금지(unique_ptr)
    RAII 객체가 복사되도록 놔두는 것은 말이 안된다.
  2. 관리하고 있는 자원에 대해 참조카운팅을 수행(shared_ptr)
    자원을 사용하고 있는 마지막 객체가 소멸될 때까지 그 자원은 소멸을 안시키는 방식이다.
    shared_ptrdeleter(삭제자) 지정을 허용하게 되는데, 삭제자란 shared_ptr이 유지하는 참조카운트가 0이 될때 호출되는 함수나 함수 객체를 말한다.
  3. 관리하고 있는 자원을 진짜로 복사합니다.
    swallow copy가 아닌 deep copy로써, 새롭게 만들어 복사한다.
  4. 관리하고 있는 자원의 소유권을 옮긴다.

Item 15 자원관리 클래스에서 관리되는 자원은 외부에서 접근할수 있도록 하자.

1
2
3
std::shared_ptr<Investment> pInv (createInvestment()); // Investment* createInvestment(); 의 함수
int dayHeld(const Investment* p1);
dayHeld(pInv); // 에러!

dayHeld 함수는 Investment*를 원하는데 shared_ptr<Investment>이므로 에러가 난다.
이로써 변환할 방법이 필요해지는데, 명시적 변환과 암시적 변환이 있음.

명시적 변환으로는 get함수를 사용하면된다.
암시적 변환으로는 operator->operator*을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
int test(int *pInt)
{
std::cout << *pInt;
}
int main()
{
std::shared_ptr<int> pInv = std::make_shared<int>();
*pInv = 5;
test(pInv.get());
return 0;
}

정리

  1. 실제 자원을 직접 관리해야 되는 기존 API들도 많기 때문에 RAII 클래스를 만들때는 그 클래스가 관리하는 자원을 얻을수 있는 방법을 열어줘야 함.
  2. 자원 접근은 명시적 변환 혹은 암시적 변환을 통해 가능함. 안전성만 따지면 명시적 변환이 대체적으로 더 낫지만, 편의성으로 보면 암시적 변환도 괜찮음.

Item 16. new 및 delete 를 사용할때는 형태를 맞추자.

1
2
3
std::string *stringArr = new std::string[100];

delete stringArr; // 정상적인 소멸과정을 거치지 못할가능성이 매우 크다.

delete 연산자가 적용하는 객체의 갯수는 소멸자가 호출하는 횟수와 같은데, 위의 예제방식으로는 객체 한개가 소멸하게 된다.
delete[]을 호출하게 되면 delete는 앞쪽의 메모리 몇 바이트를 읽고 이것을 배열의 크기로 인식하고, 해당하는 횟수만큼 소멸자를 호출한다.

정리

  1. new 표현식에 []을 썻으면 대응되는 delete도 []을 써야된다.

Item 17. new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한문장으로 만들자.

1
std::shared_ptr<int> pw(new int); // new로 생성한 객체를 스마트 포인터에 담는 코드를 독립적 문장으로 만듬.