Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Effective Modern C++ Item 7&8

514 views

Published on

2015/2/15 @Effective Modern C++ #2

Published in: Technology
  • Be the first to comment

Effective Modern C++ Item 7&8

  1. 1. Effective Modern C++ 読書会#2 Item7,8 © 2015 sprout Inc. All Rights Reserved. 1 株式会社スプラウト 高橋 明 Twitter : @Talos208
  2. 2. Item7 Distinguish between () and {} when creating objects. © 2015 sprout Inc. All Rights Reserved. 2
  3. 3. C++11の初期化方法 © 2015 sprout Inc. All Rights Reserved. 3 int x( 0 ); // (1)括弧で囲った初期化子 int y = 0; // (2) "="に続く初期化子 int z{ 0 }; // (3) 中括弧で囲った初期化子 int z = { 0 }; // (4) "="に続き、中括弧で囲った初期化子 (4)は(3)と同じに扱われる。
  4. 4. 初期化時の“=” • 初期化時の“=”は代入ではない • C++初心者が間違えやすい点 © 2015 sprout Inc. All Rights Reserved. 4 Widget w1; // デフォルトコンストラクタの呼び出し Widget w2 = w1; // 代入ではなく、コピーコンストラクタの呼び出し w1 = w2; // 代入。operator()が呼び出される
  5. 5. 「統一的な初期化構文」 // 複数要素の代入 std::vector<int> v{1,3,5}; // 1,3,5の3要素を格納したvector // メンバ変数の初期化 class Widget { ... private: int x{ 0 }; // x を初期値0で初期化 int y = 0; // y を初期値0で初期化 int z(0); // エラー! } // コピーできないオブジェクト(例:std::atmic)には“=”が使えない std::atomic<int> ai0{ 0 }; // OK std::atomic<int> ai1(0); // OK std::atomic<int> ai2 = 0; // エラー! © 2015 sprout Inc. All Rights Reserved. 5
  6. 6. “{}”内でのnarrowingの禁止 double x,y,z; ... int sum1{ x + y + z }; // エラー。doubleはintで表せない場合がある int sum2( x + y + z ); // OK。暗黙の型変換でintになる int sum3 = x + y + z; // 同上 © 2015 sprout Inc. All Rights Reserved. 6 後者2つの挙動を変えると、あまりに多くのレガシーコードを破壊してしまう。
  7. 7. 「Most Vexing Parse (最も厄介な解析)」 Widget w1(10); // 10を引数としたWidget型変数の初期化 // 同様に引数なしの初期化を意図して次のように書くと、 // コンパイラに誤って解釈される Widget w2(); // Widgetを返す関数w2の宣言に解釈される // 以下のように記述すれば意図通り Widget w3{}; // 引数なしでのWidget型変数の初期化 Widget w4; // これで良くない? © 2015 sprout Inc. All Rights Reserved. 7
  8. 8. std::initializer_listとコンストラクタ class Widget { public: Widget(int i, bool b); Widget(int i, double d); ... }; Widget w1(10, true); // 最初のコンストラクタが呼ばれる Widget w2{10, true}; // 同上 Widget w3(10, 5.0); // 2番目のコンストラクタが呼ばれる Widget w4{10, 5.0}; // 同上 © 2015 sprout Inc. All Rights Reserved. 8
  9. 9. std::initializer_listとコンストラクタ(2) class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list<long double> il); // これを追加 ... }; Widget w1(10, true); // 変化なし Widget w2{10, true}; // 3番目のコンストラクタが呼ばれる! // 10とtrueの両方がlong doubleにキャスト Widget w3(10, 5.0); // 変化なし Widget w4{10, 5.0}; // 3番目のコンストラクタが呼ばれる! // 10と5.0の両方がlong doubleにキャスト © 2015 sprout Inc. All Rights Reserved. 9
  10. 10. コピー/ムーブのコンストラクタ class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list<long double> il); operator float() const; // floatへの暗黙の型変換 ... }; Widget w4; Widget w5(w4); // w4からのコピーコンストラクタが呼ばれる Widget w6{w4}; // 3番目のコンストラクタが呼ばれる! // w4がfloatにキャストされ、さらにlong doubleに変換 Widget w7(std::move(w4)); // w4からのムーブコンストラクタが呼ばれる Widget w8{std::move(w4)}; // 3番目のコンストラクタが呼ばれる! © 2015 sprout Inc. All Rights Reserved. 10
  11. 11. std::initializer_listの優先度 class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list<bool> il); // bool型のinitializer_listのコンストラクタ // 暗黙の型変換を行うメソッドはない ... }; Widget w4{10, 5.0}; // エラー! © 2015 sprout Inc. All Rights Reserved. 11 コンパイラは(他のコンストラクタに優先して)std::initializer_list<bool>を呼ぼうと してint(10)とdouble(5.0)をboolに変換する(intまたはdoubleからboolへの暗黙の 変換はできる) この変換はnarrowingなので“{}”の中ではできず、エラーとなる。
  12. 12. std::initializer_list<T>のTへの変換がそもそもできない場合 class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list<std::string> il); // boolからstd::stringに変更 ... }; Widget w1(10, true); // 最初のコンストラクタが呼ばれる Widget w2{10, true}; // 最初のコンストラクタが呼ばれるようになる Widget w3(10, 5.0); // 2番目のコンストラクタが呼ばれる Widget w4{10, 5.0}; // 2番目のコンストラクタが呼ばれるようになる © 2015 sprout Inc. All Rights Reserved. 12
  13. 13. 初期化子のない“{}” class Widget { public: Widget(); Widget(std::initializer_list<int> il); // boolからstd::stringに変更 ... }; Widget w1; // デフォルトコンストラクタが呼ばれる Widget w2{}; // 同様にデフォルトコンストラクタが呼ばれる Widget w3(); // 最も厄介な解析! 関数宣言になる // 0要素のstd::initializer_list<>として呼びたいなら“()”か“{}”で囲む Widget w4({}); // std::initializer_list<int>のコンストラクタが呼ばれる Widget w5{{}}; // 同上 © 2015 sprout Inc. All Rights Reserved. 13 0要素のstd::initializer_listでなく、引数がないことを表す
  14. 14. std::vector<T>とstd::initializer_list std::vector<int> v1(10,20); // サイズと初期値を指定するコンストラクタが呼ばれる。 // 各要素が20で初期化された、10要素のvectorができる std::vector<int> v2{10,20}; // std::initializer_list<int>のコンストラクタが呼ばれる。 // 10と20で初期化された、2要素のvectorができる © 2015 sprout Inc. All Rights Reserved. 14 • それまでstd::initializer_list<>を引数に持つコンストラクタがないクラスに、それを 引数に持つコンストラクタを1つでも加えると、互換性を破壊するおそれがある。 • クラスの提供者は、クラスにstd::initializer_list<>を引数に持つコンストラクタを 加えるとき、熟考が必要。 • クラスの使用者も、初期化時に"()”と“{}”のどちらを使うか慎重に検討する必要が ある。
  15. 15. Pros and Cons • “{}”の使用 • 適用可能なケースが多い • 間違えてnarrowingしてしまう可能性が低い • Most Vexing Parseの心配がない • “()”の使用 • C++98の文法と互換 • std::initializer_listに関する問題がない © 2015 sprout Inc. All Rights Reserved. 15
  16. 16. テンプレート // paramsを引数としてローカルのT型のオブジェクトを作成 template<typename T, // 作成するオブジェクトの型 typename... Ts> // 使用する引数の型 void doSomeWork(Ts&&... params){ T localObject(std::forward<Ts>(params...)); // “()”を使う場合 // T localObject{std::forward<Ts>(params...)}; // “{}”を使う場合 } Std::vector<int> v; doSomeWork<std::vector<int>>(10,20); // 10要素のvectorが正しいのか? // 2要素のvectorが正しいのか? © 2015 sprout Inc. All Rights Reserved. 16 doSomeWorkの結果として何が正しいのか、doSomeWorkを書く側では判らない。 呼び出し側に依存する。 Std::make_uniqueやstd::make_shareでは、内部で“()”を使用して、それをド キュメントに記載している。
  17. 17. Things to Remember • “{}”による初期化は、適用可能なケースが多く、narrowing変換を防げ、C++の Most Vexing Parseの心配がない • コンストラクタオーバーロードの解決において、“{}”による初期化は、他にもっと適した コンストラクタがあっても、可能であればstd::initializer_listに割り当てられてしまう • “()”と“{}”とで大きな違いを産んでしまう例の一つが、std::vector<数値型>のコ ンストラクタを2つの引数で呼ぶ場合である • テンプレート内部で“()”と“{}”のどちらを使うかは難しい問題だ © 2015 sprout Inc. All Rights Reserved. 17
  18. 18. Item 8 Prefer nullptr to 0 and NULL. © 2015 sprout Inc. All Rights Reserved. 18
  19. 19. 0とNULLとnullptr • C++ではポインタが使えるところに0があった場合、ヌルポインタにフォールバックするが、 リテラルの0はintであってポインタではない。NULLもほぼ同様だが、int以外の整数型 としても使えるところが違う。 • リテラルとしてのNULLに決まった型はない。C++98では、ポインタと整数型のオー バーロードに0やNULLを渡した場合、ポインタのオーバーロードは呼ばれない。 • NULLが、例えば0Lと定義されていた場合、long→int、long→bool、0L→void* のいずれの変換も同様に正しいので、どれかを選びようがない。 • nullptrの型はstd::nullptr_tで、すべてのポインタ型に変換可能 © 2015 sprout Inc. All Rights Reserved. 19 void f(int); void f(bool); void f(void*); f(0); // f(void*)でなくf(int)が呼ばれる f(NULL); // ビルドエラーになるかもしれないが、たいていは // f(int)が呼ばれる。f(void*)は呼ばれない f(nullptr); // f(void*)が呼ばれる
  20. 20. Nullptrより0がよい場合 auto result = findRecord( /* ここに引数 */ ); if (result == 0) { ... } © 2015 sprout Inc. All Rights Reserved. 20 resultの型がわからなくても、0ならば整数型でもポインタでもどちらでもOK
  21. 21. テンプレートとnullptr • ミューテックスでガードしなければならない関数f1、f2、f3がある。単純に書くと © 2015 sprout Inc. All Rights Reserved. 21 int f1(std::shared_ptr<Widget> spw); int f2(std::unique_ptr<Widget> spw); int f3(*Widget spw); std::mutex f1m, f2m, f3m; using MuxGuard = std::lock_guard<std::mutex>; { MutexGuard g(f1m); // ロック auto result = f1(0); // スコープを抜けるところでロック解放 } ... { MutexGuard g(f2m); auto result = f1(NULL); } ... { MutexGuard g(f3m); auto result = f3(nullptr); }
  22. 22. テンプレートとnullptr • 毎回ロックするのはあんまりなので、テンプレートを使って以下のようにしてみると © 2015 sprout Inc. All Rights Reserved. 22 template<typename FuncType, typename MuxType, typeName PtrType> auto lockAndCall(FuncType func, MutexType& mutex, PtrType ptr) -> decltype(func(ptr)) // Item3参照 { MuxGuard g(mutex); return func(ptr); } auto result1 = lockAndCall(f1, f1m, 0); // エラー! auto result2 = lockAndCall(f2, f2m, NULL); // エラー! auto result3 = lockAndCall(f3, f3m, nullptr); // 成功
  23. 23. テンプレートとnullptr • 0やNULLリテラルの代入なら、その場で(ヌルポインタに)変換されて渡される。 • テンプレートに与えた場合はテンプレートとしての型解決が先に動いてしまう。 • std::shared_ptr<Widget>やstd::unique_ptr<Widget>にintを渡そうとしてエ ラー • nullptrはstd::nullptr_t型なので問題無し。 © 2015 sprout Inc. All Rights Reserved. 23
  24. 24. Things to Remember • 0やNULLでなくnullptrを使おう • 整数型とポインタのオーバーロードは止めよう © 2015 sprout Inc. All Rights Reserved. 24

×