emcjp Item 42
-
Upload
mitsunari-shigeo -
Category
Technology
-
view
638 -
download
0
Transcript of emcjp Item 42
![Page 1: emcjp Item 42](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/1.jpg)
Item42:insertionの代わりにemplacementを検討しよう
2015/9/30 emcjp
光成滋生(@herumi)
![Page 2: emcjp Item 42](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/2.jpg)
• 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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/3.jpg)
• このコードは少し性能が悪い
• "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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/4.jpg)
• 引数を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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/5.jpg)
• 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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/6.jpg)
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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/7.jpg)
• node-basedなコンテナ
• ほぼ常にcstrが使われる
• node-basedでないコンテナ
• 例 : vector, deque, string
• cstrが使われる
• deque
• emplace_frontはcstrが使われる
値の追加に関する経験則
7/12
![Page 8: emcjp Item 42](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/8.jpg)
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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/9.jpg)
• 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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/10.jpg)
• 一時オブジェクトを用意して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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/11.jpg)
• 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](https://reader031.fdocuments.net/reader031/viewer/2022030309/58f2a88c1a28abe3718b4583/html5/thumbnails/12.jpg)
まとめ
• 原則としてemplace系はinsert系より効率がよい
• 効率が悪くなることはない
• 実際には次のときに速くなる
• コンテナに追加される値がassignでなくcstrで入る
• コンテナ要素の型と渡す型が異なる
• コンテナに追加する値が重複により拒否されることが少ない
• emplace系はinsert系よりも型変換を許容しうる
12/12