일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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
- 영화 리뷰
- 반복자
- resource management class
- implicit conversion
- 참조자
- 상속
- universal reference
- virtual function
- 티스토리챌린지
- 암시적 변환
- effective modern c++
- reference
- UE4
- iterator
- std::async
- exception
- 보편 참조
- Smart Pointer
- lua
- 언리얼
- 스마트 포인터
- effective stl
- 게임
- 오블완
- c++
- operator new
- 예외
- Effective c++
- more effective c++
- 영화
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 21. std::make_unique, std::make_shared 본문
Effective C++/Effective Modern C++
[Effective Modern C++] 21. std::make_unique, std::make_shared
김디트 2025. 3. 6. 11:23728x90
항목 21. new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라
특징
- std::make_shared는 C++11에서 표준에 포함되었다.
- std::make_unique는 C++14에서 표준에 포함되었다.
- 허나 make_unique를 만드는 건 어렵지 않다.
// 배열 버전은 지원하지 않지만, 쉽게 지원하도록 추가할 수 있을 것이다.
// std namespace에 넣진 말자. 14가 되면 충돌할 것이다.
template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
- 스마트 포인터를 리턴하는 make 함수는 한 가지 더 있다.
- std::allocate_shared
- std::make_shared처럼 동작하지만, 첫 인수가 동적 메모리 할당에 쓰일 할당자 객체이다.
make 함수를 사용하여 스마트 포인터를 생성해야 하는 이유
- 타입을 여러 번 타이핑 해야 해서 유지보수가 귀찮다.
auto upw1(std::make_unique<Widget>()); // 'Widget'을 한번만 타이핑한다.
std::unique_ptr<Widget> upw2(new Widget); // 'Widget'을 여러 번 타이핑한다.
auto spw1(std::make_shared<Widget>()); // 동일
std::shared_ptr<Widget> spw2(new Widget); // 동일
- 예외 안전성이 떨어진다.
- make 함수를 사용하면 순서가 섞여 자원 누수가 발생할 여지가 차단된다.
void processWidget(std::shared_ptr<Widget> spw, int priority);
int computePriority();
// 자원 누수의 위험이 있다!
processWidget(std::shared_ptr<Widget>(new Widget), computePriority());
// 1. new Widget으로 힙에 공간 할당
// 2. std::shared_ptr<Widget>의 생성자가 실행된다.
// 3. computePriority가 실행된다.
// 허나 이 순서가 절대적인 것은 아니다.
// 아래처럼 진행될 수도 있다.
// 1. new Widget으로 힙에 공간 할당
// 2. computePriority가 실행된다.
// 3. std::shared_ptr<Widget>의 생성자가 실행된다.
// 이 경우 computePriority에서 예외가 던져지면 누수!
- 컴파일러가 생산하는 코드의 효율성이 차이난다.
std::shared_ptr<Widget> spw(new Widget); // 한 번의 할당?
// 실제로는 두 번의 할당이 발생한다.
// 제어 블록 메모리가 할당되기 때문이다.
auto spw = std::make_shared<Widget>(); // 한 번의 할당만 발생한다.
// std::make_shared가 Widget 객체와 제어블록 모두를 담을 메모리 조각을 한번에 할당한다.
make 함수를 사용하면 안 되는 경우(공용)
- 커스텀 삭제자를 지정해야 하는 경우(항목 18, 항목 19 참조)
- make 함수들에는 커스텀 삭제자를 지정할 수 있는 버전이 없다.
- 하지만 스마트 포인터들은 커스텀 삭제자를 받는 생성자를 제공한다.
auto widgetDeleter = [](Widget* pw) { ... };
// 커스텀 삭제자를 사용한 스마트 포인터들
std::unique_ptr<Widget, decltype(widgetDeleter)> upw(new Widget, widgetDeleter);
std::shared_ptr<Widget> spw(new Widget, widgetDeleter);
- 생성자로 std::initializer_list를 넘겨야 하는 경우
- 원래라면 중괄호를 사용하면 std::initializer_list 버전이 선택된다.
- make 함수들에는 어떻게 사용해야 할까?
-
// 값이 20인 요소 10개? // 요소 10, 20? auto upv = std::make_unique<std::vector<int>>(10, 20); auto spv = std::make_shared<std::vector<int>>(10, 20); // 정답은 값이 20인 요소 10개이다. // make 함수들은 내부적으로 괄호를 사용한다는 뜻이다. // 즉, 중괄호 초기치를 사용하려면 new를 사용해야 한다는 뜻이다.
- 중괄호 초기치는 완벽 전달이 불가능하기 때문이다.(항목 30 참조)
- 허나 아래와 같이 하면 우회 해결이 가능하다.
-
// std::initializer_list 객체 생성 auto initList = { 10, 20 }; // 해당 객체를 전달 auto spv = std::make_shared<std::vector<int>>(initList);
make 함수를 사용하면 안 되는 경우(shared_ptr)
- 메모리 할당을 커스텀한 경우
- 이 경우 allocate_shared와 잘 맞지 않는다.
- allocate_shared가 요구하는 메모리는 단순하게 '객체 크기 + 제어 블록 크기'이기 때문이다.
- 객체 파괴 시점과 객체의 메모리 해제 시점 사이의 시간 지연
- make_shared는 객체와 제어 블록을 한 메모리 공간에 할당하기 때문에(코드 효율성)
- 제어 블록이 파괴되기 전까지는 객체 메모리까지 할당 해제할 수 없다는 문제가 있다.
- 제어 블록은 shared_ptr과 그를 기준으로 만들어진 weak_ptr이 존재하는 한 계속 존재한다.
- 그러므로 객체 파괴 시점과 객체 점유 메모리 해제 시점 사이엔 시간 지연이 발생할 수 있다.
- weak_ptr이 남아있는 한 해제가 되지 않기 때문이다.
-
class ReallyBigType { ...} auto pBigObj = std::make_shared<ReallyBigType>(); // 아주 큰 객체 할당 ... // shared_ptr과 weak_ptr 할당 ... // shared_ptr 모두 파괴. 허나 weak_ptr은 잔존. ... // ReallyBigType 객체 메모리는 여전히 할당된 상태이다. ... // weak_ptr 모두 파괴. 메모리 해제.
- 같은 코드라도 new를 사용하면 shared_ptr 해제 시 즉시 해제가 된다.
- 메모리 블록이 공유되지 않기 때문이다.
new를 사용한 스마트 포인터 생성 시 주의점
- new의 결과를 다른 일은 전혀 하지 않는 문장에서 스마트 포인터의 생성자에 즉시 넘겨준다.
// 자원 누수의 위험이 있는 코드
processWidget(std::shared_ptr<Widget>(new Widget, cusDel), computePriority());
// 개선
std::shared_ptr<Widget> spw(new Widget, cusDel);
processWidget(spw, computePriority());
// 하지만 기존엔 rvalue로 넘기던 것이 lvalue가 되면서 복사가 되는 문제.
// 개선 2
std::shared_ptr<Widget> spw(new Widget, cusDel);
processWidget(std::move(spw), computePriority());
728x90
'Effective C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 23. std::move, std::forward (0) | 2025.03.10 |
---|---|
[Effective Modern C++] 22. unique_ptr를 사용한 pImpl 관용구의 특수 멤버 함수 (0) | 2025.03.07 |
[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++] 18. std::unique_ptr (0) | 2025.02.28 |
Comments