"Effective Modern C++" を読んだメモ。
週末に届いたので読みながら思ったことのメモ。
O'Reilly Japan - Effective Modern C++
『Effective Modern C++』勉強会まとめ - Cybozu Inside Out | サイボウズエンジニアのブログ
1章 型推論
2章 auto
項目5
- 書いたとおりの型がちゃんと使われるから auto を使うのがいいという話
- まさに例示されてた size_t 周りで嵌ったことあるけど、あれは auto じゃなくてそもそも暗黙の型変換に頼った自分が悪い
項目6
- auto で受ける前に static_cast するやつ、ETTI と呼ぶらしい、手法に名前があるってだいじ
現代C++への移行
項目7
- {} で初期化するとstd::initializer_list のオーバーロードが呼ばれる
- 個人的には () 派
項目9
項目10
項目11
- 関数定義の delete は、実装されたくないオーバーロードや引数の暗黙な変換を防ぐためにも使える、なるほど
項目13
- 外部に cbegin, cend を実装する方法、へぇーー
- template
auto cbegin(const T& t)->decltype(std::begin(t)) { return std::begin(t); }
- template
項目14
- 「関数は事前条件が満たされていると想定することが認められている」、そりゃそうだって感じだけど改めて言われるとちょっと怖い
項目15
項目17
- Rule of Three
- ムーブ演算が自動生成されないケース、全然しらなかった。。
- クラスがコピー演算を宣言している
- クラスがムーブ演算を宣言している
- クラスがデストラクタを宣言している
- ※ ただしテンプレートクラスは除く
- ムーブ演算を宣言した場合はコピーコンストラクタが生成されない
項目18
- std::unique_ptr のカスタムデリータにキャプチャなしラムダを使うときは unique_ptr インスタンスのサイズは増えない
- Empty Base Optimization が効いてるはず
- 関数ポインタの場合はそのぶんサイズが増える、気をつける
4章 スマートポインタ
項目19
- std::shared_ptr がコントロールブロックを作成するケース
- std::make_shared
- std::unique_ptr, std::auto_ptr からのコンストラクト
- 生ポインタからのコンストラクト
項目21
- std::make_shared は内包するオブジェクトとコントロールブロックを一括で確保する
- 両者の生存期間が一致する
- カスタム new/delete は使えない
項目22
- std::unique_ptr はデリータ定義も含めた型なので、これに収めるクラスはデストラクタを持つ必要がある
5章 右辺値参照、ムーブセマンティクス、完全転送
項目23
- constオブジェクトに対するムーブ要求はコピー演算に変換される
項目24
項目25
- Return Value Optimization (RVO) が可能なケースでは、コピーの省略か戻り値の move のどちらかが必ず実行される
- return std::move(variable) とかやってコンパイラの邪魔をしてはいけない
項目28
- ユニバーサル参照の展開
- 右辺値参照の右辺値参照は右辺値参照
- それ以外は左辺値参照
項目29
- ムーブ演算に対応してないクラスは意外と多いことを覚えておく
項目30
- 完全転送がエラーになるケース
- 型推論ミス
- 型またはポインタが確定しない
6章 ラムダ式
7章 並行API
項目36
- std::async のデフォルト実行ポリシーは std::launch::async | std::launch::defered
- そのおかげで、オーバーサブスクリプションやスレッド枯渇が起こることはない
- ただしスレッドプールは実装されていない
- async したスレッド上で同期的に遅延実行される可能性がある
- async の戻り値を get/wait していなければ実行されず、wait_for で無限ループに嵌まる可能性がある
- 遅延実行されたかどうかは if (wait_for(0s) == std::future_status::deferred) で判定可能
- どのスレッドで実行されるか確定できないため、スレッドローカルストレージを使うときは気をつける
- そのおかげで、オーバーサブスクリプションやスレッド枯渇が起こることはない
- 場合によっては明示的に std::launch::async を指定するのも有効
項目37
- std::thread のデストラクタは、その時点でスレッドが joinable でなければプログラム全体を停止させる仕様
- 暗黙に join すると予測不可能な待ちが発生する可能性がある
- 暗黙に detach すると不正アドレスを読み書きしてしまう可能性がある
- 例えば thread を抱えるインスタンスがデストラクトされた場合など
- RAII でデストラクト時の挙動を制御するのも有効
項目38
- std::promise は共有ステートに結果を書き込み、std::future はそこから読み込む
- shared_future を使うと共有ステートの参照カウンタがあがる
- 非同期実行されたタスクの結果を保持する std::future はデストラクト時に実行スレッドを暗黙に join する
- shared_future で複数参照されている場合は、最後の future のデストラクタが join する
項目39
- 一度しか notify しない std::condition_variable を使うなら、std::promise
を使うのがいい - std::promise
p; p.set_value(); でシグナル化 - p.get_future().wait() でシグナル待ち
- std::promise
- メリット
- コードがシンプルになる
- シグナルが来る前に wait() しても暴走しない
- spurious wakeup しない
- 注意点
- promise の共有ステートはたいてい動的確保される
- 確実に set_value() しないと待ちスレッドが永遠に待ち続ける
項目40
- std::atomic
- R/W を含む一連の命令発行順序は最適化されても入れ替わらない
- 不要な R/W が最適化で消される可能性がある
- std::atomic
::load, store なら重複しても消されないけど、load,store を含む文全体がアトミックにはならない
- volatile
- R/W を含む一連の命令発行順序が最適化で入れ替わる可能性がある
- 不要な R/W は最適化されても消されない
8章 もう一ひねり
項目41
- 値渡しでも、参照渡しと同程度のコストが期待できる条件
- 仮引数が関数内で常にコピーされる
- コピー可能
- ムーブのコストが十分に小さい
- 仮引数がコピーコンストラクトされない
- スライシングを起こさない
項目42
- → 項目21
- リソースの new と管理オブジェクトの作成タイミングが異なる場合は、emplace すると例外安全性が低くなる可能性がある
- 管理オブジェクトまで作成してからコンテナ内に完全転送されれば問題ない
- emplace 系に与える仮引数の型によっては explicit コンストラクタが呼び出される
- コンストラクトの引数を渡しているということを意識しておかないと、意図しない挙動になる可能性がある