12
newよりも make_{unique, shared}を使おう 2015/5/20 #emcjp 光成滋生(@herumi

Emcjp item21

Embed Size (px)

Citation preview

Page 1: Emcjp item21

newよりも

make_{unique, shared}を使おう

2015/5/20 #emcjp

光成滋生(@herumi)

Page 2: Emcjp item21

shared_ptr, unique_ptrを直接作る関数

std::make_shared from C++11

std::make_unique from C++14

簡易実装(本当はT[]型も必要)

std::allocate_shared

第一引数がallocator objな以外はmake_sharedと同じ

make系関数

template<class T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>(new T(std::forward<Args>(args)...)); }

2/12

Page 3: Emcjp item21

型を2回書かなくてよい

auto p(make_unique<A>());

unique_ptr<A> p(new A());

make系の利点

3/12

Page 4: Emcjp item21

リークの可能性があるバージョン

1. tmp = new A

2. calc()

3. shared_ptr<A>(tmp);

2番目で例外が投げられると1のメモリリーク

make_sharedならリークしない

より例外安全になる

void proc(shared_ptr<A> p, int x); int calc(); proc(shared_ptr<A>(new A), calc()); // メモリリークの可能性あり

void proc(shared_ptr<A> p, int x); int calc(); proc(make_shared<A>(), calc()); // メモリリークの可能性なし

4/12

Page 5: Emcjp item21

クラスのdstrを書けば大丈夫と思ってしまう

p_のnewが成功した後q_のnewで例外発生

unique_ptrを使う

よくあるリークする可能性の例(おまけ)

struct A; struct B { A *p_; A *q_; B() : p_(new A()), q_(new A()) {} ~B() { delete p_; delete q_; } };

struct A { std::unique_ptr<A> p_; std::unique_ptr<A> q_; B() : p_(new A())), q_(new A()) {}

5/12

Page 6: Emcjp item21

newしてからだと2回のメモリ確保

A本体用のメモリ

shared_ptrのcontrol block用メモリ

ex. gcc, VCともに16 + 24 byteだった

make_sharedなら1回のメモリ確保

A本来とctrl blockをまとめて確保

ex. gccなら40byte, VCなら32byteだった

make_uniqueは変わらない

make_sharedはメモリ効率がよい

struct A { int x[4]; } shared_ptr<A> p(new A);

auto p = make_shared<A>();

A(16) ctrl(24)

A ctrl

6/12

Page 7: Emcjp item21

カスタムdeleterを指定できない

回避方法は無い

直接initializer listを渡せない

initializer listはperfect-forwardできないため

一度autoに入れてから呼ぶ

make系の欠点

using Vec = std::vector<int>; make_shared<Vec>(10, 20); // 20にセットされた要素が10個 make_shared<Vec>{10, 20}; // エラー

auto il = {10, 20}; auto p = make_shared<Vec>(il); // 10, 20にセットされた要素が2個

7/12

Page 8: Emcjp item21

Tのcstrがpublicでないとき

継承関係にあるポインタを受けるとき

make系が使えないところ

struct Base { virtual Base* clone() const = 0; }; struct Derived: public Base { virtual Derived* clone() const override; }; Derived d; auto p1 = std::make_shared<Base>(d.clone()); // エラー auto p2 = std::shared_ptr<Base>(d.clone()); // 上手くいく

8/12

Page 9: Emcjp item21

Tがカスタムnew, deleteを持っているとき

通常sizeof(T)の大きさに最適化されている

make_sharedはsizeof(T) + ctrl blockのサイズになる

そもそもカスタムnewは呼ばれないのでは

カスタムnewがalignするようにしていて、Tがそれを仮定しているとエラーになるだろう(例 Eigenなど)

make_sharedの効率が悪くなるとき

9/12

Page 10: Emcjp item21

make_shared<A>の方が長くなる

weak_ptrを持っているとメモリリークに見えるかも

メモリの寿命

A ctrl

p = shared_ptr<A>(new A());

q = std::move(p);

w = q;

q.reset(); // Aのdstrが呼ばれてからfreeされる

A ctrl

w.reset();

ctrl

p = make_shared<A>();

A ctrl

q = std::move(p);

w = q;

q.reset(); // ここでAの領域は解放されない

// Aのdstrは呼ばれるがfreeはされない

w.reset();

A ctrl

A ctrl

10/12

Page 11: Emcjp item21

例外安全ではない例

作ってから渡すとちょっと非効率

moveして渡すとよい

例外安全になり損なう例もう一つ

proc(shared_ptr<A>(new A, customDeleter), calc());

shared_ptr<A> p(new A, customDeleter); proc(p, calc());

shared_ptr<A> p(new A, customDeleter); proc(std::move(p), calc());

11/12

Page 12: Emcjp item21

直接newするのに比べてmake系関数は

コードの重複を減らす

例外安全を促進する

コードがちょっと効率よくなる

欠点も理解する

カスタムdeleterが使えない

初期化リストを渡せない

メモリ管理の違いを理解する

make_sharedはweak_ptrがある限り残る

まとめ

12/12