일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- more effective c++
- reference
- exception
- 참조자
- c++
- 예외
- 오블완
- 영화 리뷰
- 보편 참조
- virtual function
- 영화
- universal reference
- resource management class
- 상속
- 언리얼
- std::async
- UE4
- implicit conversion
- operator new
- 암시적 변환
- 게임
- iterator
- effective modern c++
- 반복자
- 스마트 포인터
- effective stl
- lua
- Smart Pointer
- Effective c++
- 티스토리챌린지
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 18. std::unique_ptr 본문
Effective C++/Effective Modern C++
[Effective Modern C++] 18. std::unique_ptr
김디트 2025. 2. 28. 11:33728x90
항목 18. 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라
std::unique_ptr
- raw 포인터와 같은 크기라고 가정할 수 있다.
- 대부분의 연산에서 raw 포인터와 동일한 명령들을 실행한다.
- 독점적 소유권(exclusive ownership) 의미론을 토대로 구현되어 있다.
- 널이 아닌 std::unique_ptr는 항상 자신이 가리키는 객체를 소유하고 있다.
- 이동하면 소유권은 옮겨지고, 기존 포인터는 널로 설정된다.
- 소유권 문제로 복사는 허용되지 않는다.
- 즉, 이동 전용 타입(move-only type)이다.
- 두 가지 타입이 존재한다.
- 개별 객체를 위한 타입(std::unique_ptr<T>)
- operator[] 를 제공하지 않는다.
- 배열을 위한 타입(std::unique_ptr<T[]>)
- operator*와 operator-> 를 제공하지 않는다.
- 개별 객체를 위한 타입(std::unique_ptr<T>)
- std::shared_ptr로의 변환이 쉽고 효율적이다.
- 그렇기 때문에 팩토리 함수의 리턴 타입으로 unique_ptr은 아주 적합하다.
std::unique_ptr<Investment> up;
...
std::shared_ptr<Investment> sp = up; // 할당만으로 변환이 가능하다.
std::unique_ptr 예제
- 계통 구조(hierarchy) 안의 객체를 생성하는 팩토리 함수 리턴 타입으로 사용하기.
class Investment { ... };
class Stock : public Investment { ... };
class Bond : public Investment { ... };
class RealEstate : public Investment { ... };
template<typename... Ts>
std::unique_ptr<Investment> makeInvestment(Ts&&... params);
// 사용 예
{
...
auto pInvestment = makeInvestment( 인수들 );
...
} // *pInvestment가 파괴된다.
- 커스텀 삭제자(custom deleter)를 지정할 수도 있다.
// 람다식으로 만든 커스텀 삭제자
auto delInvmt = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<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;
}
톺아보기
- 커스텀 삭제자로 람다식을 사용하면 효율적이다.
- 아래의 커스텀 삭제자와 std::unique_ptr 객체의 크기에서 자세히 설명
- 커스텀 삭제자를 사용할 때엔 그 타입을 std::unique_ptr의 둘째 타입 인수로 지정해야 한다.
- 커스텀 삭제자에선 Investment* 객체를 delete 하는데, 정상적으로 상속되려면 하위 클래스들의 소멸자가 가상 소멸자여야 한다.
- raw 포인터를 std::unique_ptr에 할당하는 문장은 컴파일되지 않는다.
- 만약 허용된다면 raw 포인터에서 스마트 포인터로의 암시적 변환이 허용된다는 뜻이기에..
- 전달된 인수들을 new로 손실없이 전달하기 위해 std::forward를 사용했다.(항목 25 참조)
커스텀 삭제자와 std::unique_ptr 객체의 크기
- 커스텀 삭제자를 사용하면 raw 포인터와 std::unique_ptr 객체의 크기가 같다는 가정이 깨진다.
- 일반적으로 함수 포인터를 삭제자로 지정하면 1워드에서 2워드로 크기가 증가한다.
- 함수 객체를 삭제자로 지정하면 함수 객체에 저장된 상태의 크기만큼 증가한다.
- 상태 없는 함수 객체(혹은 캡쳐 없는 람다 표현식)의 경우 크기 변화가 없다.
// 캡쳐 없는 람다 타입 삭제자
auto delInvmt1 = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
// 리턴되는 std::unique_ptr은 Investment*와 크기가 같다.
template<typename... Ts>
std::unique_ptr<Investment, deltype(delInvmt1)> makeInvestment(Ts&&... args);
// 함수 형태의 삭제자
void delInvmt2(Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
}
// 리턴되는 std::unique_ptr은 최소 Investment* + 함수 포인터의 크기
template<typename... Ts>
std::unique_ptr<Investment, void (*)(Investment*))> makeInvestment(Ts&&... args);
728x90
'Effective C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 20. std::weak_ptr (0) | 2025.03.05 |
---|---|
[Effective Modern C++] 19. std::shared_ptr (0) | 2025.03.04 |
[Effective Modern C++] 17. 특수 멤버 함수(special member function) (1) | 2025.02.27 |
[Effective Modern C++] 16. thread safety한 const 멤버 함수 (0) | 2025.02.26 |
[Effective Modern C++] 15. constexpr (0) | 2025.02.25 |
Comments