effective modern cpp item18, 19

40
Effective Modern C++ 스터디 18장, 19장 이데아 게임즈 손진화

Upload: -

Post on 08-Feb-2017

15 views

Category:

Software


3 download

TRANSCRIPT

Page 1: Effective modern cpp item18, 19

Effective Modern C++ 스터디

18장, 19장

이데아 게임즈 손진화

Page 2: Effective modern cpp item18, 19

시작하기 전에..

• 생 포인터(raw pointer)의 단점

1. 하나의 객체를 가리키는지 배열을 가리키는지 구분하기 어렵다

2. 포인터 사용 후에 가리키는 객체를 삭제해야 되는지 알 수 없다 (소유)

3. 파괴 방법을 알 수 없다 (delete 사용가능 여부)

Page 3: Effective modern cpp item18, 19

생 포인터의 단점

4. delete 와 delete [] 중 뭘 써야 하는지 알기 어렵다

5. 파괴는 한 번만 하도록 신경 써야 한다

6. 포인터가 가리키는 객체가 여전히 살아있는지 알 방법이 없다

Page 4: Effective modern cpp item18, 19

스마트 포인터

• 앞의 단점들로 인한 버그를 줄이기 위해 나온 포인터

• 생 포인터를 감싸는 형태로 구현

• 생 포인터의 기능을 거의 대부분 지원한다

Page 5: Effective modern cpp item18, 19

스마트 포인터의 종류

• auto_ptr

C++ 11 에서 삭제

unique_ptr 로 대체 됨

• unique_ptr

한 명의 소유자만 허용

Page 6: Effective modern cpp item18, 19

스마트 포인터의 종류

• shared_ptr

참조 횟수를 계산

• weak_ptr

shared_ptr 가리키는 대상을 가리킬 수 있지만 참조 횟수에 영향을 주지 않음

Page 7: Effective modern cpp item18, 19

18장. 소유권 독점 자원의 관리에는 std::unique_ptr 을

사용하라

Page 8: Effective modern cpp item18, 19

특징

• 기본적으로 생 포인터와 크기가 같다

• unique_ptr 객체가 사라지면 가리키는 인스턴스도 해제된다

void f() {

unique_ptr<int> a(new int(3));

cout << *a.get() << endl;

} // a에 할당된 메모리가 해제됨!

Page 9: Effective modern cpp item18, 19

독점적 소유권

• unique_ptr 은 자신이 가리키고 있는 객체에 대한 소유권을 가지고 있으며 다른 unique_ptr 이 동시에 참조할 수 없다

• 소유권 이동은 가능

Page 10: Effective modern cpp item18, 19

예제

class Investment {

public: virtual ~Investment();

}; class Stock : public Investment {

public: ~Stock();

}; class Bond : public Investment { // ... 생략 ... }; class RealEstate : public Investment { // ... 생략 ... };

Page 11: Effective modern cpp item18, 19

예제

template<typename... Ts> std::unique_ptr<Investment> makeInvestment (eInvestmentType type, Ts&&... params) {

std::unique_ptr<Investment> pInv(nullptr); if (type == eInvestmentType.STOCK) { pInv.reset(new Stock(std::forward<Ts>(params)...);

} // ... 생략 ... return pInv; }

Page 12: Effective modern cpp item18, 19

예제

template<typename... Ts> std::unique_ptr<Investment> makeInvestment (eInvestmentType type, Ts&&... params) {

std::unique_ptr<Investment> pInv(nullptr); if (type == eInvestmentType.STOCK) { pInv.reset(new Stock(std::forward<Ts>(params)...);

} // ... 생략 ... return pInv; }

Page 13: Effective modern cpp item18, 19

예제 – 커스텀 삭제자

auto delInvmt = [](Investment* pInvestment) {

makeLogEntry(pInvestment); delete pInvestment;

}; template<typename... Ts> std::unique_ptr<Investment, decltype(delInvmt)> makeInvestment(eInvestmentType type, Ts&&... params) { std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt); // ... 생략 ... }

Page 14: Effective modern cpp item18, 19

예제 – 커스텀 삭제자

auto delInvmt = [](Investment* pInvestment) {

makeLogEntry(pInvestment); delete pInvestment;

}; template<typename... Ts> std::unique_ptr<Investment, decltype(delInvmt)> makeInvestment(eInvestmentType type, Ts&&... params) { std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt); // ... 생략 ... }

Page 15: Effective modern cpp item18, 19

예제 (C++ 14)

template<typename... Ts> std::unique_ptr<Investment, decltype(delInvmt)> makeInvestment(eInvestmentType type, Ts&&... params) {

auto delInvmt = [](Investment* pInvestment) {

makeLogEntry(pInvestment); delete pInvestment;

};

std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);

// ... 생략 ... }

Page 16: Effective modern cpp item18, 19

커스텀 삭제자

• 반환 형식의 크기는 함수 포인터의 크기 만큼 증가한다

• 삭제자가 함수 객체일 때는 함수 객체에 저장된 상태의 크기만큼 증가한다

• 갈무리 없는 람다 표현식 권장

Page 17: Effective modern cpp item18, 19

배열

• 개별 객체와 배열 객체 포인터 따로 지원

• std::unique_ptr<T>

색인 연산 [] 지원 안됨

• std::unique_ptr<T[]>

->, * 지원 안됨

Page 18: Effective modern cpp item18, 19

shared_ptr로 변환

• shared_ptr 로의 변환이 쉽고 효율적임

std::shared_ptr<Investment> sp = makeInvestment(…);

Page 19: Effective modern cpp item18, 19

결론

• unique_ptr은 독점 소유권 의미론을 가진 자원의 관리를 위한, 작고 빠른 이동전용 똑똑한 포인터이다.

• 기본적으로 자원 파괴는 delete를 통해 일어나나, 커스텀 삭제자를 지정할 수 도 있다. 상태 있는 삭제자나 함수 포인터를 사용하면 unique_ptr 객체의 크기가 커진다

Page 20: Effective modern cpp item18, 19

결론

• unique_ptr를 shared_ptr로 손쉽게 변환할 수 있다

Page 21: Effective modern cpp item18, 19

19장. 소유권 공유 자원의 관리에는 std::shared_ptr를

사용하라

Page 22: Effective modern cpp item18, 19

Idea

• 객체 메모리를 일일이 관리하긴 귀찮지만 안 쓸 때 바로 해제하고 싶어!

C++ 가비지 콜렉터를 지원하는 언어

shared_ptr

Page 23: Effective modern cpp item18, 19

How?

참조횟수

• 해당 자원을 가리키는 shared_ptr 의 개수

• 참조횟수가 0이 되면 자원을 파괴한다

Page 24: Effective modern cpp item18, 19

성능

• shared_ptr의 크기는 생 포인터의 2배이다 자원을 가리키는 포인터 + 참조 횟수를 저장하는 포인터

• 참조 횟수를 증감하는 연산은 원자적 연산이다 - 멀티 스레드환경에서도 안전함을 보장해야 하기 때문 - 이동 생성의 경우 횟수가 변하지 않는다

Page 25: Effective modern cpp item18, 19

성능

• 참조 횟수를 담는 메모리도 동적으로 할당된다 - 객체는 동적 할당 될 때 참조 횟수를 따로 저장하지 않기 때문 - 내장 형식도 shared_ptr로 선언가능 - std::make_shared를 선언하면 비용을 줄일 수 있다(커스텀 삭제자 지원 안됨)

Page 26: Effective modern cpp item18, 19

커스텀 삭제자

• unique_ptr 선언 unique_ptr<Widget, decltype(loggingDel)> upw (new Widget, loggingDel); 타입 std::_ptr<Widget, lambda []void (Widget *pw)->void>

• 삭제자에 따라 ptr 크기가 변한다

Page 27: Effective modern cpp item18, 19

커스텀 삭제자

• shared_ptr 선언 std::shared_ptr<Widget> spw (new Widget, loggingDel); 타입 std::shared_ptr<Widget>

• 삭제자를 지정해도 크기가 변하지 않는다

Page 28: Effective modern cpp item18, 19

커스텀 삭제자

auto costomDeleter1 = [](Widget *pw) {delete pw; };

auto costomDeleter2 = [](Widget *pw) {delete pw; };

unique_ptr<Widget, decltype(costomDeleter1)> upwDeleter1 (new Widget, costomDeleter1);

unique_ptr<Widget, decltype(costomDeleter2)> upwDeleter2 (new Widget, costomDeleter2);

vector<unique_ptr<Widget, decltype(costomDeleter1)>> vupw;

vupw.push_back(upwDeleter1);

vupw.push_back(upwDeleter2); // error!

Page 29: Effective modern cpp item18, 19

커스텀 삭제자

auto costomDeleter1 = [](Widget *pw) {delete pw; };

auto costomDeleter2 = [](Widget *pw) {delete pw; };

std::shared_ptr<Widget> spwDeleter1(new Widget, costomDeleter1);

std::shared_ptr<Widget> spwDeleter2(new Widget, costomDeleter2);

vector<shared_ptr<Widget>> vspw;

vspw.push_back(spwDeleter1);

vspw.push_back(spwDeleter2); // ok

Page 30: Effective modern cpp item18, 19

제어블록

• shared_ptr 이 관리하는 객체 1개당 제어블록 1개가 생성된다

Page 31: Effective modern cpp item18, 19

제어블록 생성 규칙

• std::make_shared는 항상 제어블록을 생성한다 shared_ptr을 가리키는 객체를 새로 생성하기 때문에 그 객체에 대한 제어블록이 이미 존재할 가능성이 없다

• shared_ptr이나 weak_ptr로부터 shared_ptr을 생성하면 기존 포인터에서 가지고 있는 제어블록을 참고한다

Page 32: Effective modern cpp item18, 19

제어블록 생성 규칙

• 고유 소유권 포인터(unique_ptr, auto_ptr)로부터 shared_ptr 객체를 생성하면 제어블록이 생성된다 - 고유 소유권은 제어블록을 사용하지 않기 때문에 해당 객체에 대한 제어블록이 없다고 보장한다 - 고유 소유권 포인터는 shared_ptr로 이동하면 해당 객체에 대한 권한을 상실한다

Page 33: Effective modern cpp item18, 19

제어블록 생성 규칙

• 생 포인터로 shared_ptr을 생성하면 제어블록이 생성된다

auto praw = new int(11);

shared_ptr<int> spwFromRaw1(praw);

shared_ptr<int> spwFromRaw2(praw); // 미정의 행동! shared_ptr<int> spwGood(new int(11)); // 미정의 행동 방지

Page 34: Effective modern cpp item18, 19

this 포인터

class Widget;

vector<shared_ptr<Widget>> processWidgets;

class Widget

{

public:

void process()

{

processWidgets.emplace_back(this);

}

};

// 이미 해당객체를 가리키는 다른 shared_ptr이 있다면 문제가 됨

Page 35: Effective modern cpp item18, 19

this 포인터

class Widget;

vector<shared_ptr<Widget>> processWidgets;

class Widget : public enable_shared_from_this<Widget>

{

public:

void process()

{

processWidgets.emplace_back(shared_from_this());

}

}; // Curiously Recurring Template Pattern

// 문제 해결?

Page 36: Effective modern cpp item18, 19

this 포인터

class Widget; vector<shared_ptr<Widget>> processWidgets;

class Widget : public enable_shared_from_this<Widget> { public: template<typename ... Ts> static shared_ptr<Widget> Create(void) { return shared_ptr<Widget>(new Widget()); }

void process() { processWidgets.emplace_back(shared_from_this()); } private: Widget() {}; };

Page 37: Effective modern cpp item18, 19

비싼 비용?

• make_shared로 shared_ptr을 생성하면 제어 블록할당 비용은 무료다

• 제어블록에 있는 가상 함수는 많이 호출되지 않는다

• 원자 연산은 기계어 명령에 대응되기 때문에 비용이 그렇게 크지 않다

• 그래도 부담스럽다면 unique_ptr로 선언한 뒤 업그레이드 하면 된다

Page 38: Effective modern cpp item18, 19

그 외

• 단일 객체 관리를 염두에 두고 설계되었기 때문에 operator[]를 제공하지 않는다

Page 39: Effective modern cpp item18, 19

결론

• shared_ptr는 임의의 공유 자원의 수명을 편리하게 관리할 수 있는 수단을 제공한다

• 대체로 shared_ptr객체의 크기는 unique_ptr의 두 배이며, 제어 블록에 관련된 추가 부담을 유발하며, 원자적 참조 횟수 조작을 요구한다.

Page 40: Effective modern cpp item18, 19

결론

• 자원은 기본적으로 delete를 통해 파괴되나 커스텀 삭제자도 지원한다. 삭제자의 형식은 shared_ptr의 형식에 아무런 영향도 미치지 않는다

• 생 포인터의 형식의 변수로부터 shared_ptr을 생성하는 일은 피해야 한다