[Effective Modern C++] 2015 스콧 마이어스 저
" C++11 과 C++ 14 를 효과적으로 사용하는 42가지 방법
4장 똑똑한 포인터
1. auto_ptr
2. unique_ptr
3. shared_ptr
4. weak_ptr
항목 18. 소유권 독점 자원의 관리에는 std::unique_ptr을 사용하라
unique_ptr :
1. 자신이 가리키는 객체를 소유한다.
2. 이동하면 원본 포인터는 nullptr이 된다. (이동 전용 형식)
3. 복사는 허용되지 않는다.
4. 파괴는 delete를 통해 일어나지만 커스텀 삭제자를 사용하도록 지정하는 것도 가능
class Test
{
};
auto Deleter(Test* p)
{
delete p;
}
int main()
{
// 일반적으로, 함수 포인터를 삭제자로 지정한 경우에는
// std::unique_ptr의 크기가 1 워드에서 2 워드로
// 증가한다.
// 삭제자가 함수 객체일 때에는 std::unique_ptr의
// 크기가 그 함수 객체에 저장된 상태의
// 크기만큼 증가한다.
// 상태 없는 함수 객체(이를테면 갈무리 없는 람다
// 표현식이 산출한)의 경우에는 크기 변화가
// 없으며, 따라서 가능하면 람다 표현식을 선호하는
// 것이 바람직하다
// 출처 : http://ajwmain.iptime.org/programming/book_summary
auto deleter = [](Test* p)
{
delete p;
};
// ptr1 크기 : 4
std::unique_ptr<Test, decltype(deleter)> ptr1(new Test, deleter);
// ptr2 크기 : 48
std::unique_ptr<Test, std::function<void(Test*)>> ptr2(new Test, Deleter);
}
항목 19. 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라
shared_ptr :
1. 객체를 가리키던 마지막 shared_ptr이 객체를 더 이상 가리키지 않으면 객체를 파괴
2. 자원의 참조횟수로 파괴 시점을 판단
3. shared_ptr의 크기는 생 포인터의 두 배이다. (포인터 + 자원 횟수 담을 장소)
4. 참조 횟수를 담을 메모리는 반드시 동적으로 할당된 것이어야 한다.
5. 참조 횟수의 증가와 감소가 반드시 원자적 연산이어야 한다.
6. 이동 생성이 복사 생성보다 빠르다.
7. shared_ptr 는 unique_ptr 와 다르게 커스텀 삭제자의 타입을 지정해주지 않아도 되는 유연함을 갖고 있다.
8. 커스텀 삭제자가 아무리 커져도 shared_ptr 의 크기는 변하지 않는다. 그 이유는 앞에서 말했던 두 개의 포인터 중 로우 포인터를 제외한 다른 하나의 포인터가 관리를 하기 때문이다. 이 포인터가 가리키는 객체는 바로 제어 블록(control block)이다.
출처 : https://modoocode.com/252
항목 20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라
weak_ptr :
1. 그 객체의 참조 횟수에는 영향을 주지 않는다.
2. weak_ptr은 shared_ptr의 참조자이다.
3. shared_ptr로부터 복사 생성/대입 연산이 가능하며 직접 access는 불가능하다.
4. 직접 access를 하려면 lock 메서드를 통해 shared_ptrfh convert 한 뒤 chared_ptr의 get 메서드를 사용해야 한다.
5. expired 함수를 통해 자신이 참조하고 있는 shared_ptr의 상태를 체크할 수 있다.
6. weak_ptr의 잠재적인 용도로는 캐싱, 관찰자 목록 그리고 shared_ptr 순환 고리 방지가 있다.
항목 21. new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라.
auto pWidget = std::make_unique <Widget>();// make 함수 사용
std::unique_ptr<Widget> pWidget(new Widget); // make 함수 비사용
1. new를 사용하면 생성할 때 타입을 두 번 작성해야 한다.
2. make 방식은 Widget에 대한 할당과 control Block의 할당을 한 번에 처리
* make 함수를 사용하지 말아야 할 때
1. make 함수는 삭제자를 지정할 방법이 없음.
2. std::initializer_list를 매개변수로 갖는 생성자가 오버로드 되어 있는 클래스, 중괄호 초기치를 생성하려면 반드시 new를 사용해야 한다.
항목 22. Pimpl 관용구를 사용할 때에는 특수 멤버 함수들을 구현 파일에서 정의하라.
PIMPL(Pointer to implementation)
: Header 파일에 impl class 혹은 struct를 전방 선언을 하고 .cpp 파일에서 세부 구현을 정의
//ttest.h
// 헤더에 include 할 필요 없음
class ttest
{
public:
struct Aimple;
Aimple* pImpl;
};
//ttest.cpp
#include <vector>
struct ttest::Aimple
{
std::vector<int> vec;
};
unique 함수로 선언할 경우
특수 멤버 함수(special member functions) 자동 생성 규칙에 따라 소멸자가 생성되어야 한다.
컴파일러는 그 소멸자 내에서 pImpl 소멸자를 호출하는 코드를 인라인으로 삽입한다.
그런데 대부분 C++ 표준 라이브러리 unique_ptr 구현체에서는 raw pointer가 incomplete type을 가리키는지 static assertion 하게 된다.
근데 인라인으로 삽입된 소멸자 입장에서 pImpl은 incomplete type이다.
따라서 컴파일 에러가 발생하고, 빌드가 안된다.
소멸자를 직접 만들어 주면 해결된다.
// 컴파일러는 형식의 정의를 보게 되면 그 형식을 완전한 형식으로 간주한다.
// 선언부에 정의만 하고 구현은 구현부에서 한다.
// 소멸, 이동
// 출처: https://infoscoco.com/50 [What is that?]
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // 정의
Widget::Widget(Widget&& rhs) = default;
Widget& Widget::operator=(Widget&&rhs) = default;
'Programming' 카테고리의 다른 글
이펙티브 모던 C++_6 (0) | 2020.12.06 |
---|---|
이펙티브 모던 C++_5 (0) | 2020.11.29 |
이펙티브 모던 C++_3 (0) | 2020.11.15 |
이펙티브 모던 C++_2 (0) | 2020.11.08 |
이펙티브 모던 C++_1 (0) | 2020.11.01 |