Upload
mitsunari-shigeo
View
808
Download
2
Embed Size (px)
Citation preview
newよりも
make_{unique, shared}を使おう
2015/5/20 #emcjp
光成滋生(@herumi)
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
型を2回書かなくてよい
auto p(make_unique<A>());
unique_ptr<A> p(new A());
make系の利点
3/12
リークの可能性があるバージョン
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
クラスの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
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
カスタム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
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
Tがカスタムnew, deleteを持っているとき
通常sizeof(T)の大きさに最適化されている
make_sharedはsizeof(T) + ctrl blockのサイズになる
そもそもカスタムnewは呼ばれないのでは
カスタムnewがalignするようにしていて、Tがそれを仮定しているとエラーになるだろう(例 Eigenなど)
make_sharedの効率が悪くなるとき
9/12
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
例外安全ではない例
作ってから渡すとちょっと非効率
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
直接newするのに比べてmake系関数は
コードの重複を減らす
例外安全を促進する
コードがちょっと効率よくなる
欠点も理解する
カスタムdeleterが使えない
初期化リストを渡せない
メモリ管理の違いを理解する
make_sharedはweak_ptrがある限り残る
まとめ
12/12