스마트 포인터를 사용하는 이유
1. 생성과 소멸 작업이 자유로움
- 시기 조절 가능
- 생성될 때 자동으로 기본값 null
- 객체를 가리키고 있던 포인터가 소멸될 때 자동으로 그 객체 삭제 가능 (자원 낭비 막음)
2. 복사와 대입 동작 가능
3. 역참조 동작 가능
18. 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라
std::unique_ptr
- 스마트 포인터
- 하나의 소유자만을 허용 (복사안됨)
- 이동하면 소유권이 이동한 대상 포인터로 옮겨짐. (이동은됨)
- delete 연산자를 이용해 수동으로 메모리 해제
- 포인터 소멸시 포인터가 가리키고 있던 자원도 같이 소멸
- 개발할 때 내장 배열보다는 std::array/vector/string 등이 더 나은 선택인데,
std::unique_ptr의 배열 버전은 내장 배열이라서 C스타일 API를 다루어야 하는 경우를 제외하면 쓸일이 딱히없음.
팩터리 함수
- 클래스의 인스턴스를 만드는 일은 서브클래스가 함.
- std::unique_ptr 의 용도 중 하나. (다른용도로는 Pimpl 관용구의 구현 매커니즘도 있음)
- std::unique_ptr의 팩터리 함수
custom deleter
- 해당 자원이 파괴될 때 호출되는 임의의 함수
(또는 람다 표현식으로부터 산출되는 것들을 포함한 함수 객체)
- 객체를 생성할 때 커스텀 삭제자를 사용하도록 지정하여 생성
- 커스텀 삭제자(람다 표현식)
makeInvestment 함수에서 생성하는 객체의 실제 타입이 무엇이든,
그 객체는 람다 표현식 안에서 Investment* 객체로서 delete된다.
=> 기반 클래스 포인터로 파생 클래스의 객체 삭제
=> Investment의 소멸자가 가상 소멸자이어야 함
std::shared_ptr 로의 변환
- unique_ptr을 위의 ptr로 쉽게 변환 가능
- 팩토리 함수는 소유권에 대한 정보를 미리 알 수가 없다.
- 위와 같은 코드로 shared_ptr로 변환이 가능하다. (shared -> unique의 변환은 불가능)
19. 소유권 공유 자원의 관리에는 std::shared_ptr을 사용하라
std::shared_ptr
- API가 단일 객체를 가리키는 포인터만 사용하는 것으로 설계
- 배열관리 못함
(operator[ ]를 제공하지 않음, 파생 -> 기반 포인터 변환이 단일 객체에서만 합당함)
- regerence count라는 std::shared_ptr들의 개수를 알려주는 참조 카운트가 있는데
std::shared_ptr이 최후의 공유 포인터임을 알게해줌.
- 참조 카운트를 담을 메모리는 반드시 동적으로 할당해줘야 하고,
참조 카운트를 가리키는 포인터의 메모리 때문에 크기가 생포인터의 2배이다.
- 커스텀 삭제자도 지원되긴함. 삭제자의 타입은 상관없음.
- 더 많은 비용이 들긴 하지만, 객관적으로 크지 않은 비요으로 동적할당 자원의 수명이 자동으로 관리됨
잘못된 코드
- std::shared_ptr 생성자에 생포인터를 넘겨주는 일은 피하라
=> std::make_shared를 사용하라 (커스텀 삭제자를 사용할땐 불가능)
- std::shared_ptr 생성자를 생 포인터로 호출할 수 밖에 없다면
std::shared_ptr<Widget> spw1(new Widget, loggingDel); 처럼 직접 new로 전달
- spw1이 가리키는 Widget 객체를 가리키는 std::shared_ptr를 만들고 싶다면
std::shared_ptr<Widget> spw2(spw1); 처럼 작성 (동일한 제어블록 사용)
++
- this 포인터로 std::shared_ptr를 안전하게 생성하려면
class Widget: public std::enable_shared_from_this<Widget>{ ... }
다음 템플릿을 그 클래스의 기반클래스로 삼는다.
20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라
std::weak_ptr
- std::shared_ptr로부터 얻을 수 있음
- weak_ptr은 expired( ) 함수의 반환값으로 만료되었는지 알 수 있다.
- weak_ptr의 역참조 방법
1) lock 멤버함수를 사용한다.
std::shared_ptr<Widget> spw1 = wpw.lock();
auto spw2 = wpw.lock( )
2) std::shared_ptr의 생성자에 std::weak_ptr을 넘겨준다.
std::shared_ptr<Widget> spw3(wpw);
- 잠재적인 용도
1) 캐싱
2) 관찰자 목록
3) std::shared_ptr 순환고리 방지
21. new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라
make 함수 사용 시 장점
1. 코드가 중복되지 않는다.
2. 예외 안전성이 향상됨
3. 효율성 (make_shared)
new를 직접 사용한 메모리 할당은 Widget 객체를 위한 할당과 제어 블록을 위한 할당으로 2번 일어남.
하지만 make_shared를 사용하면 한번의 할당으로 가능
make 함수에는 커스텀 삭제자를 지정하는 기능이 없기 때문에 중괄호 초기치를 전달해야함.
- new를 직접 사용
- 우회책
: auto 타입 추론을 이용해서 중괄호 초기치로 std::initializer_list 객체를 생성한 후 make 함수에 넘겨준다.
22. pimpl 관용구를 사용할 때에는 특수 멤버 함수들을 구현 파일에서 정의하라
불완전한 타입(lmpl)을 가리키는 포인터 선언 -> pimpl이 위와 같은 능력을 활용
std::unique_ptr에만 적용 (std::shared_ptr 적용 X)
Widget 클래스의 소멸자는 unique_ptr의 소멸자가 대신 해주기 때문에 필요없음.
클래스 헤더 : 특수 멤버 함수들 선언
구현 파일: 선언한 함수 구현
이동 연산을 지원하지 않음
소멸자에서 발생했던 문제 발생
-> pimpl을 파괴하려면 lmpl이 완전한 타입이어야함.
-> 따라서 이동 연산들의 정의를 구현 파일로 옮기면됨
(헤더에서 선언, 구현에서 정의)
'C++ > Modern Effective' 카테고리의 다른 글
[Modern Effective C++] ch02.auto (0) | 2020.02.14 |
---|