[Effective Modern C++] 18. 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라
std::unique_ptr은 독점적 소유권 의미론을 재현하는 클래스이다. null이 아닌 std::unique_ptr은 항상 자신이 가리키는 개체를 소유한다. 그에 따라 복사를 허용하지 않으며, 오직 이동만 가능하다. 그리고 raw포인터와 거의 같은 크기를 가지기 때문에 메모리와 CPU 성능이 넉넉하지 않더라도 사용하기에 충분하다.
std::unique_ptr객체는 자신이 파괴될 때, 가리키는 자원 또한 함께 파괴된다.
std::unique_ptr객체의 파괴는 delete를 통해 일어나나 커스텀 삭제자를 사용해서 삭제할 수도 있는데 상태가 있는 삭제자나 함수포인터를 사용하면 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의 크기는 raw포인터와 거의 같다고 하였는데, 커스텀 삭제자를 사용하면 달라진다.
일반적으로, 함수 포인터를 삭제자로 지정한 경우에는 std::unique_ptr의 크기가 1워드에서 2 워드로 증가하며 삭제자가 함수 객체일 경우에는 그 함수 객체에 저장된 상태의 크기만큼 증가한다.
상태가 없는 함수 객체(ex.람다식..)의 경우에는 크기 변화가 없으며, 따라서 가능하면 람다식을 선호하는 것이 바람직하다.
// 람다식으로 작성한 경우
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);
마지막으로 팩터리 함수에서 std::unique_ptr을 std::shared_ptr로 손쉽게 변환할 수 있는데, 팩터리 함수는 자신이 돌려준 객체를 호출자가 독점적으로 소유하려 하는지 소유권을 공유하려 하는지 미리 알 수 없다.
팩터리 함수가 std::unique_ptr을 반환한다면 호출자는 가장 효율적인 스마트 포인터를 얻게 되며 상황에 따라 아래처럼 std::shared_ptr로 변환하여 쓸 수도 있다.
std::shared_ptr<Investment> sp = makeInvestment(params);
하지만 반대로 std::shared_ptr을 std::unique_ptr로 변환하는 것은 불가능하다.
'Books > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라 (0) | 2022.09.18 |
---|---|
[Effective Modern C++] 19. 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라 (0) | 2022.09.18 |
[Effective Modern C++] 17. 특수 멤버 함수들의 자동 작성 조건을 숙지하자 (0) | 2022.09.11 |
[Effective Modern C++] 16. const멤버 함수를 스레드에 안전하게 작성하자 (1) | 2022.09.11 |
[Effective Modern C++] 15. 가능하면 항상 constexpr을 사용하자 (0) | 2022.09.11 |
댓글
이 글 공유하기
다른 글
-
[Effective Modern C++] 20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라
[Effective Modern C++] 20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라
2022.09.18 -
[Effective Modern C++] 19. 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라
[Effective Modern C++] 19. 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라
2022.09.18 -
[Effective Modern C++] 17. 특수 멤버 함수들의 자동 작성 조건을 숙지하자
[Effective Modern C++] 17. 특수 멤버 함수들의 자동 작성 조건을 숙지하자
2022.09.11 -
[Effective Modern C++] 16. const멤버 함수를 스레드에 안전하게 작성하자
[Effective Modern C++] 16. const멤버 함수를 스레드에 안전하게 작성하자
2022.09.11