Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

C2C++11Level1

5.393 Aufrufe

Veröffentlicht am

特にC言語をマスターした方は、C言語で満足してしまいC++11の便利機能を使わない方が多い。
私も半年前まではそうでした。
しかし、C++11を少し触っていくと、C言語にはない 安全なコードを書くことが出来ます
しかも、C言語と比べほとんどオーバーヘッドがないのが素敵

主にCゲンガーを対象に、更に安全で開発効率が良くなる 新機能のTipsを
実際にC++を使っている技術者視点でシリーズ化しようとおもいます

みなさんのツッコミ よろしくお願いします。

Veröffentlicht in: Technologie
  • Loggen Sie sich ein, um Kommentare anzuzeigen.

C2C++11Level1

  1. 1. C2C++11 〜Level 1 〜 安全なコードを求めて 株式会社ムラサメ研究所 みやたけ ゆき
  2. 2. 自己紹介 みやたけゆき  主な開発言語 C++、アセンブラ、GPGPU、シェー ダー、Java、スマホネイティブ  最近のC++開発案件 MOゲームサーバ(boost.asio) 3Dゲームエンジンfor iOS&Android(OpenGLES2.0) 動画エンコーディング(h264) 3Dレンダリングエンジンfor Windowd(DirectX11) セキュリティーソフト for Windows ゲーム(cocos2d-x)、UNITY用 C++プラグイン ゲーム(コンシュマー)  C++11歴は 5ヶ月。それまでは生ポインターと memcpyを愛用
  3. 3. 題字(花文字) 睦月 翔子 花文字アーティスト 花文字とは、龍や鳥等の開運絵柄を用い て即興で描く文字です。オーダーメイド の開運アイテムとして人気がございます。 http://hana-moji.jp/
  4. 4. はじめに  C言語は最も利用されている言語だが、メモリ やポインタ等を抽象化せずに直接使用してき たため、それらの不具合やセキュリティ問題 が多い  C++では、速度を損なわずに それらを抽象化 し、安全で高速なコードを提供する  今回はLevel1 という事で、危険性の高いポイ ンタやメモリを中心に、C++の安全なコード に変更する方法を書く  今回のメインターゲットは、C言語に満足して、 C++11に移行出来てない人、具体的には生ポ インタやmemcpyを書いている技術者
  5. 5. ポインタ
  6. 6. 過去の問題  過去において、各社が独自にスマートポインタを作っ ていた  各社インタフェースが違い習得コストが高い  それぞれにに癖があり、むしろ余計にメモリリークを していた事も・・ お前は今までにprintfしたCComPtrの参照カウンタの 数を覚えているか?  スレッドセーフでないもの、オーバーヘッドの大きい もの等があった  auto_ptrには問題がある、boostライブラリは導入が 大変だ 標準化されたので安心して使いましょう!!
  7. 7. 問題  C言語でよく難しいと言われる代表が ポインタ  C言語で不具合が最もいものが ポインタ 下記のコードは問題がある。何故? char *p = new char[10]; ・・・何かの処理 delete[] p;
  8. 8. 答え char *p = new char[10]; ・・ここで例外発生したらpが開放されない delete[] p; このような過ちは熟練者でもよくやってしま う。 生ポインタは使うべきではない
  9. 9. unique_ptr 先ほどの例は unique_ptrを使うと unique_ptr<char[]> p(new char[10]); ・・・何かの処理 これでOK。スコープを抜けると デストラクタで処 理されるので 明示的にdeleteする必要もないし 例外発生時も正しく デストラクタが開放してくれる ポインタはunique_ptrに置き換えるべき
  10. 10. unique_ptr  unique_ptrでラップしたポインタは、普 通のポインタのように扱う事が出来る  生ポインタと実行速度は変わらない  互換性等の為に、生のポインタが欲しい場 合はp.get() で取得できる  release()メソッドを呼べば、ポインタと の関係性を破棄(unique_ptrのスコープ が消えてもdeleteされない) 従来のポインタとの互換性も十分
  11. 11. おまけ  C++14では make_uniqueが追加された unique_ptr<char[]> = make_unique<char[]>(20); // 配列数を指定 newが不要になった newに対応したdeleteがないと気持ち悪い人に朗報  カスタムデーリータを設定する事が出来る 例えばファイルハンドルがスコープ外に出たらcloseするとか  参照カウンタをもったポインタは shared_ptr&weak_ptrを使う ポインタは全て、スマートポインタに変えるべき
  12. 12. 配列
  13. 13. 宣言 C言語では配列は int a[10]; C++では std::array<int,10> ar; 二次元配列 int aa[10][2]; std::array<std::array<int,2>, 10> aar; 配列の順番が逆になる事に注意 配列の順番が逆になる のが気になる人は Sprout とか使うといいらしいよ
  14. 14. 概要 std::array<int,10> ar; ar[1] = 10; 普通の配列と同じように使える 中身は配列なのでオーバーヘッド無し 宣言は多少面倒だが std::arrayを使いましょう
  15. 15. std::arrayを使うメリット  配列数を取得 std::array ar.size() 生配列 sizeof(a)/sizeof(*a) あるいは std::size(a) 簡潔でわかりやすい
  16. 16. std::arrayを使うメリット  アルゴリズム std::array ar.fill(5); 生配列 std::fill(std::begin(a), std::end(a), 5); 簡潔でわかりやすい
  17. 17. std::arrayを使うメリット  比較 std::array if( ar1 == ar2 ) 生配列 if( std::equal(std::begin(a1), std::end(a1), std::begin(a2))) 簡潔でわかりやすい
  18. 18. std::arrayを使うメリット  範囲外アクセス std::array ar.at(100); // throw std::out_of_range ar[100]; // 不正アクセス 生配列 a [100]; // 不正アクセス atを使うと範囲外アクセスで例外発生
  19. 19. std::arrayを使うメリット  型の安全性 std::array void hoge(std::array<int,5> &a) // int 5個配列以外 はコンパイルエラー 生配列 void hoge(int *a) // どんなサイズでもOKになるので、 安全でない void hoge(int (&a)[5]) // int 5個配列以外はコンパイ ルエラー 生配列の文法はわかりにくい
  20. 20. まとめ  実はC++11になると、生配列も機能 拡張され、stl::algorithm を使えば、 std::arrayとほぼ同じ事が出来る  だが、std::arrayを使う方がコード も読みやすいしデメリットはないの で積極的に使うべき
  21. 21. おまけ  C++17では std::make_arrayが提案されている std::array<int,4> a{{0,1,2,3}}; が std::make_arrayを使うと auto a(std::make_array(0,1,2,3)); と書け、要素数や型を指定しなくてもよくなる https://gist.github.com/lichray/6034753 ここに実装例があるので、上記のコードを参考にすれば C++11でも std::make_arrayを使用する事が可能 使ってますが とっても便利です!
  22. 22. memcpy
  23. 23. memcpy  memcpyは最も危険な関数の一つ。 memcpyによるバッファオーバーラ ンで苦しんだCゲンガーは多い  memcpyは原始的で、型も無視し機 械的にメモリをコピーする  C++ではmemcpyを極力使わず std::copyや代入演算子等の安全なも のを使うべきだ
  24. 24. 構造体のコピー struct A{ int a[10]: }a; struct B{ int a[100]: }b; memcpy( a, b, sizeof(B) ); 当然 バッファオーバーラン!! 構造体のコピーは単純に a=b; と、代入演算子を使いましょう 当然、この場合は型が違うのでコンパイルエラー 型が同じであれば当然コピー出来ます
  25. 25. 配列のコピー int a[10],b[100]; memcpy( a, b, sizeof(b) ); 当然バッファオーバーランします 配列は std::arrayを使うんでしたね std::array<int,10> a; std::array<int,100> b; a=b; 代入演算子を使いましょう 当然、この場合は型が違うのでコンパイルエラー 同じ型であれば当然コピー出来ます
  26. 26. 配列の一部コピー int a[10],b[100]; memcpy( a+2, b+2, sizeof(int)*10 ); 当然バッファオーバーランします std::array<int,10> a; std::array<int,100> b; std::copy(std::begin(b)+2, std::begin(b)+12, std::begin(a)+2); ダメです。メモリリークします std::copyを使っても安心できません ただし、Clangだと上記のコードで aの範囲 チェックされているようで、リークしません!! Clangすばらしい!
  27. 27. まとめ  構造体のコピーは代入で  配列はarrayを使えば代入でOK  途中のコピーはstd::copyで。ただし、 バッファオーバーランには気をつけて ね  std::copyはstlコンテナ(vector等)とも 相性が良いのでそちらを使うべき  どうしても原始配列を使う場合も、フ リー関数を使えばSTLアルゴリズムを使 える
  28. 28. おまけ 速度比較  memcpyは連続領域を一括コピーする 一般的には、レジスタの最大サイズでコ ピーを行うため高速である  代入やstd::copyでは基本1要素毎のコピー であるため、速度はmemcpyより遅い事も ある  しかし構造体には アラインがあり、 memcpyはアラインまでコピーする為、場 合によっては 代入のほうが速い 速度差が顕著な場合を除いて、危険な memcpyを使うべきではない
  29. 29. cast
  30. 30. cast  C言語のcastは1種類で、意図しない castをする可能性がある  C++には const_cast、static_cast、 dynamic_cast、reinterpret_cast の4つがあり、それぞれに何を意図し たcastかを指定出来る
  31. 31. const_cast  constとvolatileを外すcast void a(const int&n){ const_cast<int&>(n)=5; } こんな事書くと constで渡してるのに値を書き換えられてしまう!! よっぽどな事が無い限り使ってはいけない非常に危険なcast 個人的にはvolatileの制御で時々使うがマニア向け volatile int flag; // flagは最適化対象外になる int *p = const_cast<int*>(&flag); // pは最適化対象 参考: http://qiita.com/YukiMiyatake/items/1191ab03b6c0b5a22876 最も使ってはならないcast
  32. 32. dynamic_cast  実行時にキャストが行われる class A{virtual void boo(){};}; class B : public A{}; class C{virtual void boo(){};}; B bb; A *a = dynamic_cast<A*>(&bb); B *b = dynamic_cast<B*>(a); C *c = dynamic_cast<C*>(a); B*からA*へのアップキャストは安全 A*からB*へのダウンキャストは危険をともなう場合がある A*からC*は継承関係がないのでエラー値 nullが返って来る なるべく使わないcast
  33. 33. static_cast  コンパイル時に決定されるキャスト double d = static_cast<double>(3) / 2; コンパイル時に確定される、非常に安全な キャスト 暗黙的な変換等あるいは void*からの変換を行う どんどん使ってよろしい cast
  34. 34. reinterpret_cast  最解釈キャスト float f = 1.0f; int f0 = *(reinterpret_cast<int*>(&f)); 強制的になんでもキャストする(型を無視) 上記は f0には 1ではなく、そのCPUの浮動小数1.0fのバ イトデータが入る (ex 0x3f800000) 継承関係があってもアップキャスト、ダウンキャスト等 を行わない 最も不具合を出す危険なcast
  35. 35. まとめ  C++で追加された4つのキャストを使う事  static_cast以外を使う時は、設計ミスをし ている可能性を疑おう  実行時エラーが起きた際に キーワードで検 索で絞れる C++スタイルのキャストを使っていると不 具合も探しやすい  他人がソースを読む場合に意図が伝わる つまり、C++のキャストを使うべき
  36. 36. explicit
  37. 37. explicit  引数1つのコンストラクタ=型変換コ ンストラクタ が存在する場合は、暗黙的型変換が 行われるので、それを阻止する事が できる
  38. 38. explicit class A{ public: A( int n ){cout << "construct " << n << endl; }; }; auto main() -> int { [](const A &a){}(2); return 1; }; const A& の引数を期待しているが intで渡せる 暗黙的型変換により intから class Aが 暗黙的に生成されている!! 一般的には、明示的な型変換しか受け付けないほうが良い 暗黙的型変換を想定していない場合は 禁止すべきだ
  39. 39. explicit class A{ public: explicit A( int n ){}; }; と、explicitを使えば暗黙的型変換はコンパイルエ ラーになる もちろん、明示的にキャストすればOK [](const A &a){}(static_cast<A>(2)); 特別な意図がないかぎりは、型変換コンストラク タは explicitするべき
  40. 40. 型エイリアス
  41. 41. 型エイリアス  今までは typedefを使い型を定義し ていたが、型が一番右に来て直感的 に分かりにくい  また、関数や配列の場合は一番右で もなく真ん中に来るので更にわかり にくい  typedefではテンプレートを使えない  using 型 = ほげ; と、直感的に分かりやすく書ける!
  42. 42. 型エイリアス // typedef typedef int hoge1; typedef int hoge2[10]; typedef hoge1 (*hoge3)(hoge2); // 型エイリアス using hoge1 = int; using hoge2 = int[10]; using hoge3 = hoge1(*)(hoge2); どちらも同じ動作をするが、明らかに型エイ リアスのほうが可読性が高い
  43. 43. template // typedef template<class T> typedef std::map<int, T> int_map; int_map<double> m; // コンパイルエラー // 型エイリアス template<class T> using int_map= std::map<int, T>; int_map<double> m; // OK typedefでは テンプレートに対応出来ない
  44. 44. enum class
  45. 45. enum class  基本は enum  enum classはスコープがついている ので名前がかぶらなくなった  内部型のデフォルトが処理系依存で なくなった  intへの暗黙的キャストがなくなり安 全性が増した
  46. 46. スコープ int A; enum hoge{ A, // int Aとシンボルがぶつかりエラー B }; enum class hoge2{ A, // スコープを持つのでかぶらない B }; 以前のenumは、スコープを持たないため、他のシンボ ルとかぶる為、namespaceを付けたり、サフィックスを つけてたが enum classでは独自スコープを持つため 問題ない
  47. 47. 暗黙的型変換 enum class hoge { A, B }; int a = hoge::A; // コンパイルエラー enumではintへの暗黙的型変換は許可されているが enum class では許可されない static_castで内部型に明示的にキャストする // enum classの内部型 using hoge_t = std::underlying_type<hoge>::type ; hoge_t a = static_cast<hoge_t>(hoge::A);
  48. 48. 最後に  C++11の機能を使うにあたって、まず は安全なコードを意識しましょう  生ポインタはスマートポインタに変更  配列は std::arrayを使用する  memcpyの使用を避ける  castはC++のキャストを使う  typedefより 型エイリアスを使う  列挙は enum classを使う

×