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.

C2C++11Level1

3,942 views

Published on

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

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

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

Published in: Technology

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を使う

×