본문 바로가기

Programming

이펙티브 모던 C++_4

[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