예외나 기타 비전형적 제어 흐름(이를테면 때 이른 함수 반환이나 루프로부터의 break 등)때문에 그러한 일련의 소유권 이전이 중도에 가로 차이면,결국에는 관리되는 자원을 소유하고 있던 std::unique_ptr의 소멸자가 호출되며, 결과적으로 관리되는 자원이 파괴됩니다.
(* 이 규칙에는 몇 가지 예외가 있습니다. 그 예외들은 대부분 비정상적인 프로그램 종료에서 비롯됩니다. 예외가 스레드의 주 함수(예를 들어 프로그램의 초기 스레더의 경우에는 main) 바깥으로까지 전파되거나 noexcept 명세가 위반되면(Chapter(14) 참고) 지역 객체들이 파괴되지 않을 수 있으며, std::abort나 어떤 종료 함수(즉 std::Exit나 std::exit, std::quick_exit)가 호출되면 지역 객체들은 절대로 파괴되지 않습니다.)
그러한 파괴는 기본적으로 delete를 통해서 일어나지만, std::unique_ptr 객체를 생성할 때 커스텀 삭제자(custom deleter)를 사용하도록 지정하는 것도 가능합니다. 커스텀 삭제자는 해당 자원의 파괴 시점에서 호출되는 임의의 함수(또는, 람다 표현식으로부터 산출되는 것들을 포함한 함수 객체)입니다. makeInvestment가 생성한 객체를 직접 delete로 파괴할 수 없고 먼저 로그 항목을 기록한 후에 파괴해야 한다면, makeInvestment를 다음과 같이 구현하면 될 것입니다. (코드 다음에 설명이 나오니, 코드만 보고 그 의도가 명확하게 이해되지 않는 부분이 있어도 걱정하지 말기 바랍니다.)
auto delInvmt = [](Investment* pInvestment) // 커스텀 삭제자(람다 표현식)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
tmeplate<typename... Ts> // 반환 형식이 바뀌었음
std::unique_ptr<Investment, decltype(delInvmt)>
makeInvestment(Ts&&... params)
{
std::unique_ptr<Investment, decltype(delInvmt)> // 돌려줄 포인터
pInv(nullptr, delInvmt);
if ( /* Stock 객체를 생성해야 하는 경우 */ )
{
pInv.reset(new Stock(std::forward<Ts>(params)...));
}
else if ( /* Bond 객체를 생성해야 하는 경우 */ )
{
pInv.reset(new Bond(std::forward<TS>(params)...));
}
else if ( /* RealEstate 객체를 생성해야 하는 경우 */ )
{
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
}
return pInv;
}
이 함수의 작동 방식은 잠시 후에 설명하고, 먼저 호출자의 관점에서 이 함수를 어떤 식으로 사용해야 하는지부터 살펴봅시다. makeInvestment 호출 결과를 하나의 auto 변수에 저장한다고 가정할 때, 독자는 삭제 시 특별한 처리가 필요한 자원을 다루고 있음을 알지 못하고 알 필요도 없는 호사를 누리게 됩니다. 그러한 '무지'는 축복입니다. 애초에 std::unique_ptr를 사용한다는 것은, 프로그램의 모든 경로에서 파괴가 정확히 한 번만 일어남이 보장됨을 뜻할 뿐만 아니라, 자원의 파괴 방식에 대해 신경 쓸 필요가 전혀 없음을 뜻하기 때문입니다. 그 모든 것을 std::unique_ptr가 자동으로 처리해줍니다. 클라이언트의 관점에서 makeInvestment의 인터페이스는 그야말로 유쾌합니다.
다음 사항들을 이해한다면, 구현 역시 상당히 멋지다는 점을 깨닫게 될 것입니다.
- delInvmt는 makeInvestment가 돌려준 객체에 대한 커스텀 삭제자입니다. 모든 커스텀 삭제 함수는 파괴하라 객체를 가리키는 포인터 하나를 받으며, 그 객체를 파괴하는 데 필요한 일들을 수행합니다. 지금 예에서 삭제자가 해야 할 일은 makeLogEntry를 호출한 후 delete를 적용하는 것입니다. 람다 표현식을 이용해서 delInvmt를 생성하는 것은 편리할 뿐만 아니라, 잠시 후에 보겠지만 통상적인 함수를 작성하는 것보다 더 효율적이기도 합니다.
- 커스텀 삭제를 사용할 때에는 그 형식을 std::unique_ptr의 둘째 형식 인수로 지정해야 합니다. 지금 예에서 삭제자의 형식은 delInvmt의 형식이며, makeInvestment의 반환 형식이std::unique_ptr <Investment, decltype(delInvmt)>인 것은 바로 그 때문입니다. (decltype에 관해서는 Chpater(3)을 참고.)
- makeInvestment의 기본 전략은 널 std::unique_ptr를 만들어서 적절한 형식의 객체를 가리키게 한 후 돌려주는 것입니다. 커스텀 삭제자 delInvmnt를 pInv에 연관시키기 위해, pInv 생성 시 delInvmt를 둘째 형식으로 지정합니다.
'컴퓨터과학' 카테고리의 다른 글
C++ (19-1) 소유권 공유 자원의 관리에는 std::shared_ptr를 사용할 것 (0) | 2020.06.19 |
---|---|
[Effective C++] (18-3) 소유권 독점 자원의 관리에는 std::unique_ptr를 사용할 것 (0) | 2020.06.17 |
[Effective C++] (똑똑한 포인터) / Chapter(18-1) 소유권 독점 자원의 관리에는 std::unique_ptr를 사용할 것 (0) | 2020.06.14 |
[Effective C++] (17-4) 특수 멤버 함수들의 자동 작성 조건을 숙지할 것 / (똑똑한 포인터) (0) | 2020.06.13 |
[Effective C++] (17-3) 특수 멤버 함수들의 자동 작성 조건을 숙지할 것 (0) | 2020.06.11 |