C++/Effective Modern C++
[Effective Modern C++] 항목 18. 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라
pppgod
2019. 11. 3. 15:38
std::unique_ptr 는 독점적 소유권 의미론을 재현하는 클래스이다. 그에 따라 복사를 허용하지 않으며, 오직 이동만 가능하다. std::unique_ptr 는 raw 포인터와 거의 같은 크기를 갖는다. 메모리와 CPU 성능이 넉넉하지 않더라도 사용하기에 충분하다는 뜻이다.
std::unique_ptr 객체는 자신이 파괴될 때, 가르키는 자원 또한 함께 파괴된다. std::unique_ptr 가 파괴될 때 수행되는 커스텀 삭제자를 사용할 수 있다. 팩터리 패턴의 함수의 예시를 살펴보자.
auto delInvmt = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)>
makeInvsetment(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(Bond stock(std::forward<Ts>(params)...));
}
else if ( /* RealEstate 객체를 생성해야 하는 경우 */ )
{
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
}
return pInv;
}
위의 함수를 사용하는 호출자는 반환받은 객체의 소멸이 오직 한 번만 일어난다는 것과 소멸 방식에 대해 신경을 쓰지 않아도 된다는 장점이 생긴다.
C++14 에서 위의 코드를 작성한다면 보다 간결하고 캡슐화된 방식으로 구현이 가능하다.
template<typename... Ts>
auto makeInvsetment(Ts&&... params)
{
auto delInvmt = [](Invsetment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
std::unique_ptr<Investment, decltype(delInvmt)>
pInv(nullptr, delInvmt);
if ( /* Stock 객체를 생성해야 하는 경우 */ )
{
pInv.reset(new stock(std::forward<Ts>(params)...));
}
else if ( /* Bond 객체를 생성해야 하는 경우 */ )
{
pInv.reset(Bond stock(std::forward<Ts>(params)...));
}
else if ( /* RealEstate 객체를 생성해야 하는 경우 */ )
{
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
}
return pInv;
}
std::unique_ptr 의 크기
std::unique_ptr 의 크기는 raw 포인터와 거의 같다고 하였다. 그런데 커스텀 삭제자를 사용하는 경우에는 std::unique_ptr 의 크기가 1 워드에서 2 워드로 증가한다. 삭제자가 함수 객체일 때에는 그 함수 객체에 저장된 상태의 크기만큼 증가하고, 갈무리 없는 람다식의 경우에는 크기의 변화가 없다. 따라서 두 가지 모두 구현 가능하다면 람다식을 사용하는 것이 바람직하다.
// 람다식으로 작성한 경우
auto delInvmt = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)> // 반환 형식은 Investment* 와 같은 크기
makeInvsetment(Ts&&... params);
// 함수 형태로 작성한 경우
void delInvmt2(Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename... Ts>
std::unique_ptr<Investment,
void (*)(Investment(*)> // 반환 형식은 Investment* 에 함수 포인터의 크기를 더한 크기
makeInvsetment(Ts&&... params);
참고 서적
스콧 마이어스, Effective Modern C++