emcjp Item 42

12
Item42:insertionの代わりに emplacementを検討しよう 2015/9/30 emcjp 光成滋生(@herumi)

Transcript of emcjp Item 42

Page 1: emcjp Item 42

Item42:insertionの代わりにemplacementを検討しよう

2015/9/30 emcjp

光成滋生(@herumi)

Page 2: emcjp Item 42

• std::vector<T>には次の2個のpush_backしかない

• したがって

• このコードは次のコードと同じ

vectorのpush_back

std::vector<std::string> vs; vs.push_back("abc");

void push_back(const T&); void push_back(T&&);

vs.push_back(std::string("abc"));

2/12

Page 3: emcjp Item 42

• このコードは少し性能が悪い

• "abc"からstringオブジェクトtmpが作られる

• tmpはpush_backに渡されrvalue参照xに束縛される

• stringのmove cstrを用いてxのコピーがvsの中に作られる

• push_back完了後にtmpのdstrが呼ばれる

• tmpのcstr/dstrを無くしたい

• 文字リテラルをvsに直接渡して中で文字列を作りたい

• emplace_backを使おう

問題点と改善案

vs.push_back(std::string("abc"));

template<class... Args> void emplace_back(Args&&... args);

3/12

Page 4: emcjp Item 42

• 引数をperfect forwardするのでstringのcstrに渡せる任意の引数を渡せる

• push/insertに対応するemplaceがある

emplace系の利点

vs.emplace_back(50, 'x'); // 50個の'x'

push/insert系 対応するemplace

push_back emplace_back

push_front emplace_front

insert emplace

insert with hint (mapなど) emplace_hint

insert_after emplace_after

4/12

Page 5: emcjp Item 42

• insert系は挿入されるオブジェクトを渡す

• emplace系は挿入されるオブジェクトを作るためのcstr引数を渡す

• emplaceには挿入されるオブジェクトも渡せるので 次のコードは同じ

• insert系でできることが全てできて理論的にはemplace系の方が効率がよいなら、いつでもemplaceを使うべきか?

• 100%そうとは言い切れない

• チューニングするときはベンチマークをとること

insertとemplace

std::string s("abc"); vs.push_back(s); vs.emplace_back(s);

5/12

Page 6: emcjp Item 42

1.追加する値がコンテナ内で生成( ≠代入)されるとき

• 一時オブジェクトを生成してmove代入する例

• このコードでvs[0]にstringを生成する実装は殆どない

• 大抵は一時オブジェクトを生成してmove代入する

• VS2015はgcc/clangより何か余計にcstr/dstrしてる

• コンテナに値を追加する方法にcstrとassignのどちらを使うか実装者に任せるなんて!

emplaceの方が効率がよい3条件(1/2)

vs.emplace_back("abc");

assert(!vs.empty()); vs.emplace(vs.begin(), "abc");

6/12

Page 7: emcjp Item 42

• node-basedなコンテナ

• ほぼ常にcstrが使われる

• node-basedでないコンテナ

• 例 : vector, deque, string

• cstrが使われる

• deque

• emplace_frontはcstrが使われる

値の追加に関する経験則

7/12

Page 8: emcjp Item 42

2.コンテナ要素の型Tと引数の型が異なるとき

• もともとemplaceは一時的なTのcstr/dstrを減らすのが目的

• Tを渡すなら、emplaceが速くなる理由はない

3.コンテナが新しい値をduplicateとして破棄することが殆どないとき

• emplace系は値xを作ってからコンテナにxがあるかを調べる

• 既にあればxは破棄する

• 重複が多ければcstr/dstrは無駄が多いことになる

• 先ほどの例はこの3個の条件を満たす

emplaceの方が効率がよい3条件(2/2)

8/12

vs.emplace_back("abc"); // 1. コンテナ内で値をcstrする // 2. コンテナ要素の型と異なる // 3. 破棄することはない

Page 9: emcjp Item 42

• shared_ptrを使うとき

• push_backを呼ぶ前にshared_ptrが一時的な値tmpを作る

• push_backで例外が投げられてもtmpは正しく破棄される

• new Aの後、emplace_backの中でノードを作ったときに例外が投げられるとリークする

emplaceが駄目なとき

std::list<std::shared_ptr<A>> ptrs; void killA(A* p); // カスタムdeleter

ptrs.push_back(std::shared_ptr<A>(new A, killA)); // あるいは ptrs.push_back({new A, killA});

ptrs.emplace_back(new A, killA);

9/12

Page 10: emcjp Item 42

• 一時オブジェクトを用意してmoveする

解決方法(cf. Item 21)

std::shared_ptr<A> spw(new A, killA); ptrs.push_back(std::move(spw)); // あるいは ptrs.emplace_back(std::move(spw));

10/12

Page 11: emcjp Item 42

• explicit std::regex(const char*);

• explicitがついているのでnullptrでコピー初期化はできない

• std::vector<std::regex> rs;

• insert系はコピー初期化を用いる

• emplace系は直接初期化を用いる

予定外の型を受け入れることがある

std::regex r = nullptr; //コピー初期化:コンパイルエラー std::regex r(nullptr); //直接初期化:実行時エラー

rs.push_back(nullptr); // コンパイルエラー rs.emplace_back(nullptr); // 実行時エラー

11/12

Page 12: emcjp Item 42

まとめ

• 原則としてemplace系はinsert系より効率がよい

• 効率が悪くなることはない

• 実際には次のときに速くなる

• コンテナに追加される値がassignでなくcstrで入る

• コンテナ要素の型と渡す型が異なる

• コンテナに追加する値が重複により拒否されることが少ない

• emplace系はinsert系よりも型変換を許容しうる

12/12