Upgrade to Pro — share decks privately, control downloads, hide ads and more …

グリとブランのC++講座 ~C++98(STL)と、ほんのちょっとのC++11~

グリとブランのC++講座 ~C++98(STL)と、ほんのちょっとのC++11~

C++入門者向けに、STLのコンテナを中心に解説してあります。

HEXADRIVE

May 24, 2013
Tweet

More Decks by HEXADRIVE

Other Decks in Programming

Transcript

  1. もちろん C++ じゃなくて GC が使える Java や C#、動的言語の Ruby や

    Python で書いたほうが 開発効率は格段に上がるニャ。
  2. でも C++ と Java/C# だったらスループット性能で 10倍(※) 、C++ と Ruby/Python だったら

    1000倍も違うと言われているんだニャ。 ※JITコンパイラを備えるJavaVM では、Cよりも速くなることがあ るようです。 ただし、ネットワークに対する 入出力が多いゲームでは、シス テムコールを呼び出す前後での バッファオーバーフローの チェック等が毎回生じるため、C との速度差が生まれるとのこと。
  3. 簡単に C++ の歴史と機能紹介 As for C++ C++は、汎用プログラミング言語の一つである。 C言語の拡張として開発された。 拡張はクラスの追加に始まり、仮想関数、多重定義、多重継承、テンプレート、例外処理 といった機能が続いていった。

    静的な型システムを持ち、手続き型プログラミング・データ抽象・オブジェクト指向プログラミン グ・ジェネリックプログラミングといった複数のプログラミングパラダイムをサポートするマルチパラダ イムプログラミング言語である。 詳しくは wiki を見てね! http://ja.wikipedia.org/wiki/C%2B%2B
  4. 簡単に C++ の歴史と機能紹介 C++98 • 初めての標準化 • STL に代表される標準ライブラリの追加 C++03

    • C++ 98での不具合の修正等 C++11 • C++98以来初の大きな改訂 • マルチスレッドやジェネリックプログラミングを強化 詳しくは wiki を見てね! http://ja.wikipedia.org/wiki/C%2B%2B
  5. 簡単に C++ の歴史と機能紹介 クラス - Class class Hoge { public:

    // どこからでもアクセス可能 Hoge(void) : _member(0) {} // コンストラクタ virtual ~Hoge(void) {} // デストラクタ protected: // 継承した子クラスからのみアクセス可能 void Function(void); private: //クラス内からのみアクセス可能 int _member; }; オブジェクトの設計図。クラスから生成したオブジェクトをインスタンスという。 クラスにはインスタンス生成時に呼ばれるコンストラクタと、 インスタンスが破棄される時に呼ばれるデストラクタを定義できる。 また、クラス内の関数(メンバ関数)や変数(メンバ変数) のアクセスレベルも指定できる。 Hoge* hoge = new Hoge();
  6. 簡単に C++ の歴史と機能紹介 クラス - Class class Parent { public:

    Parent(void) : _member(0) {} virtual ~Parent(void) {} protected: int _member; }; class Child: public Parent { public: Child(void) {} virtual ~Child(void) {} void OutputMember(void) { std::cout << _member << std::endl; } }; クラスを継承することで、親クラスの機能を持ち合わせたまま、子クラスで拡張することもできる。
  7. 簡単に C++ の歴史と機能紹介 オーバーライド - Override class Parent { public:

    void Function(void) { std::cout << "parent" << std::endl; } }; class Child : public Parent { public: void Function(void) { std::cout << "child" << std::endl; } }; 子クラスが親クラスのメンバ関数を上書きできる。 Parent* parent = new Child(); Child* child = new Child(); parent->Function(); // parent child->Function(); // child
  8. 簡単に C++ の歴史と機能紹介 仮想関数 - Virtual Function class Parent {

    public: virtual void Function(void) { std::cout << "parent" << std::endl; } }; class Child : public Parent { public: void Function(void) { std::cout << "child" << std::endl; } }; メンバ関数をオーバーライドした子クラスを、親クラスの型へアップキャストしながら格納した時に、 メンバ関数の動作を上書きすることができる。 Parent* parent = new Child(); Child* child = new Child(); parent->Function(); // child child->Function(); // child
  9. 簡単に C++ の歴史と機能紹介 Tips: デストラクタの落とし穴 class Parent { public: Parent()

    { std::cout << "Parent Constructor" << std::endl; } ~Parent() { std::cout << "Parent Destructor" << std::endl; } }; class Child : public Parent { public: Child() { std::cout << "Child Constructor" << std::endl; } ~Child() { std::cout << "Child Destructor" << std::endl; } }; 継承される可能性のあるクラスのデストラクタは仮想関数にすること。 仮想関数にしないと、ポリモーフィズムを活用した際に想定外の挙動となることがある。 以下サンプル。
  10. 簡単に C++ の歴史と機能紹介 Tips: デストラクタの落とし穴 Child child; /* ▼出力(特に問題なし) Parent

    Constructor Child Constructor Child Destructor Parent Destructor */ Parent* pParent = new Child(); // 親の型にアップキャスト delete(pParent); /* ▼出力(宣言された型が優先されるため、子のデストラクタが呼ばれない!) Parent Constructor Child Constructor Parent Destructor */
  11. 簡単に C++ の歴史と機能紹介 純粋仮想関数 - Pure Virtual Function class Parent

    { public: virtual void Function(void) = 0; }; class Child : public Parent { public: void Function(void) { std::cout << "child" << std::endl; } }; 実装が無い、宣言のみの仮想関数。継承して実装をしなければインスタンス化できない。 //Parent* pParent= new Parent(); コンパイルエラー! Parent* pParent= new Child(); pParent->Function(); // child
  12. 簡単に C++ の歴史と機能紹介 多重定義 - Overload class Hoge { public:

    void Function(int n) { std::cout << n << std::endl; } void Function(float f) { std::cout << f << std::endl; } }; 引数を変えた、同名の関数や演算子を複数定義できる。 Hoge hoge; hoge.Function(1); hoge.Function(5.8f);
  13. 簡単に C++ の歴史と機能紹介 テンプレート - Template template <typename T> class

    Hoge { public: void Function(T value) { std::cout << value << std::endl; } }; コンパイル時にコードを生成できる。 ジェネリックプログラミング(データ形式に依存しないプログラミング形式) に用いられる。 コンパイル時に生成されるため、使用を誤るとコード量自体は多くないのに 生成された実行ファイルのサイズが肥大化してしまうという危険性もあるため注意が必要。 Hoge<int> hoge1; hoge1.Function(1); Hoge<float> hoge2; hoge2.Function(0.3f);
  14. 簡単に C++ の歴史と機能紹介 テンプレートメタプログラミング - Template Metaprogramming template <int N>

    struct Factorial{ enum { value = N * Factorial<N - 1>:: value}; }; template <> struct Factorial<0> { enum { value = 1 }; }; テンプレートを応用して再帰的にコードを生成することにより、動的な計算量を削減することができる。 // これらは実行時に計算することがないため、処理コストが少ない。 int x = Factorial<4>::value; // 24 int y = Factorial<0>::value; // 1
  15. Standard Template Library (STL) As for STL テンプレートを最大限に生かす構成を取っており、コンテナ・イテレータ(反復子)・アルゴリズ ム・関数オブジェクトと呼ばれる各要素から成っている。 C++におけるジェネリックプログラミングのはしりとなった。

    オブジェクトを格納するコンテナとそれを操作するアルゴリズム、その接点としてイテレータが存 在し、コンテナとアルゴリズムが互いに完全に独立しているのも特徴の一つである。 詳しくは wiki を見てね! http://ja.wikipedia.org/wiki/Standard_Template_Library
  16. Standard Template Library (STL) コンテナ:vector 連続要素を保持する動的配列。 STLの中では一番シンプルで有用な場面も多い。 リファレンスは http://www.cppll.jp/cppreference/cppvector.html を参照。

    特徴的なのは、格納された要素がメモリ空間において隣接しているということ。 つまり、 &v[n] == &v[0] + n ということになる。 そのため、末尾への要素追加における計算量は定数時間 O(1) となる。 ちなみに中間への要素追加における計算量は線形時間 O(n) である。 vector はメモリの再割り当てを、それが必要になったタイミングで行う。 (末尾への要素追加時に領域が確保できていなかったら、一定の領域を確保するなど) 再割り当てを行う場合は一定のコストがかかるが、reserve を使用することにより、 再割り当ての機会を減らすことも可能。 API 呼び出しによるオーバーヘッドはあるが、malloc や new[] よりもメモリリークの回避や 配列アクセスにイテレータを使用できたりと利点が多いため、vector は多くの場面で活用できる。
  17. Standard Template Library (STL) コンテナ:deque (Double Ended Queue) 双方向キュー。 簡単に言うと、vector

    を末尾への追加だけではなく、双方向に追加できるようにしたもの。 リファレンスは http://www.cppll.jp/cppreference/cppdeque.html を参照。 vector と同じく、先頭や末尾への要素追加は O(1) 、中間への要素追加は O(n)。 内部的にはリングバッファというデータ構造になっており、確保した領域の先頭アドレスに要素がある状態 で push_front した場合は、確保した領域の末尾に追加されるというイメージ。 先頭要素の位置は保持しているため、push_front したからといって全ての要素がメモリ空間上で 再配置されることはない。 先頭への要素追加/削除が行える点で vector よりも使い勝手が良いが 、 reserve ができない、要素がメモリ空間上で連続しているという保証はないという欠点もあるため、 どちらを採用するかは処理によって検討する必要がある。
  18. Standard Template Library (STL) コンテナ:list 双方向連結リスト。 リファレンスは http://www.cppll.jp/cppreference/cpplist.html を参照。 vector

    や deque と違い、任意の位置への要素追加にかかる計算量が定数時間 O(1) となる。 しかし、連結リストでデータを保持するため、ランダムアクセスは遅い。(先頭、末尾からの探索となる) また、必要な分しかメモリを確保しないという特徴もある。 メモリキャッシュに乗らないので、deque を使ったほうがパフォーマンス的に上がる事が多いかもしれない。
  19. Standard Template Library (STL) コンテナ:set 要素の重複を許さない集合。 リファレンスは http://www.cppll.jp/cppreference/cppset.html を参照。 要素の追加、検索、削除の計算量は対数時間

    O(log n) となる。 vector と比べて重複を許さないというアルゴリズム的な差異があって使い勝手が良いが、 一般的に Red-Black ツリー(http://ja.wikipedia.org/wiki/%E8%B5%A4%E9%BB%92%E6%9C%A8) によって実装されており、それによりツリーを構築するために相当なオーバーヘッドがかかってしまう。 そのため、メモリキャッシュに乗ることも念頭に入れて、vector で重複チェックを行った上で 要素を追加する方がパフォーマンス的には優れているとも言える。
  20. Standard Template Library (STL) コンテナ:map 連想コンテナ。キーと値を関連付けて管理する。 リファレンスは http://www.cppll.jp/cppreference/cppmap.html を参照。 set

    と同じで Red-Black ツリーなので、要素の追加、検索、削除の計算量は 対数時間 O(log n) となる。 使用する際のデメリットも set と同様。 しかし、キーを指定の型に設定できることはメリットとして大きいため、使用したい場面も多い。
  21. Standard Template Library (STL) コンテナアダプタ:stack 末尾からの挿入と末尾からの取り出しをサポート。 Last In First Out

    (LIFO) とも呼ばれる。 リファレンスは http://www.cppll.jp/cppreference/cppstack.html を参照。 コンテナアダプタ:queue 末尾からの挿入と先頭からの取り出しをサポート。 First In First Out (FIFO) とも呼ばれる。 リファレンスは http://www.cppll.jp/cppreference/cppqueue.html を参照。 ※コンテナアダプタとはコンテナを内部に保持して、限定的に機能を公開しているもの。 単体では使いにくいが、protected 継承することにより、内部のコンテナにアクセスできる。 それにより独自の拡張が可能となっている。
  22. Standard Template Library (STL) イテレータ - Iterator イテレータはポインタと同じような使い方でコンテナの各要素にアクセスするために用意される。 イテレータの中にもいくつか種類があるが、代表的なものは以下のものである。 ▼前方イテレータ

    - forward_iterator 要素の読み書きが行える。ただし前方への移動しかできない。 ▼双方向イテレータ - bidirectional_iterator 双方向へ移動しながら要素の読み書きが行える。 list や set、map はこれ。 ▼ランダムアクセスイテレータ - random_iterator ランダムアクセスが可能な双方向イテレータ。 vector や deque はこれ。
  23. Standard Template Library (STL) アルゴリズム - Algorithm コンテナに対する汎用的な処理を集めたもの。 非常に多くの関数が用意されているため、STLを使用する際の恩恵として大きい。 カテゴリとして、四種類に分類される。

    ・検索・操作 (http://www.cppll.jp/cppreference/cpp_algo_find.html) ・配列操作 (http://www.cppll.jp/cppreference/cpp_algo_manip.html) ・ソート関連 (http://www.cppll.jp/cppreference/cpp_algo_sort.html) ・その他 (http://www.cppll.jp/cppreference/cpp_algo_other.html)
  24. Standard Template Library (STL) 関数オブジェクト - Function Object 関数呼び出し演算子をオーバーロードすることにより、オブジェクトでありながら関数のように呼び出せる。 関数ポインタやポリモーフィズムの代わりになり得る。

    アルゴリズムの中には関数オブジェクトを引数で与えるものもあり、 それによって更にアルゴリズムの汎用性を向上させている。 また、C++11 から標準化されたラムダ式を使うことで、より簡潔に書ける。
  25. 【C++】サンプル【書いてみた】 公開するインターフェースクラスと実装部分を切り離す // 実装部分。公開しない。 class Model : public IModel {

    public: Model(void) : _position() {} virtual ~Model(void) {} void Update(); void Draw(); void SetPosition(const Vector3& position) { this->_position = position; } const Vector3& GetPosition(void) const { return this->_position; } private: Vector3 _position ; };
  26. 【C++】サンプル【書いてみた】 typedef std::vector<int> IntList; IntList list; list.push_back(1000); for (int i

    = 0; i < 10 ; ++i) { list.push_back(std::rand()); } std::sort(list.begin(), list.end()); IntList::iterator it = std::find(list.begin(), list.end(), 1000); list.erase(it, list.end()); リストにランダムな整数値を入力し、閾値を基準に削除する
  27. 【C++】サンプル【書いてみた】 ポリシークラス - Policy Class struct NoChecking { template <typename

    T> static bool CheckPointer(T*) { return true; } }; struct NullChecking { template <typename T> static bool CheckPointer(T* p) { return (p != nullptr); } }; struct BadPointerDoNothing { template <typename T> static T* HandleBadPointer(T* p) { std::cout << "pointer is moldy" << std::endl; return p; } }; 構文的に同じインタフェースの下で、異なる処理を提供する。 ポリシーを使うクラスは、それが使うそれぞれのポリシーに対してひとつのテンプレート引数を持って、 テンプレート化されている。 このため必要なポリシーを選択することが出来る。
  28. 【C++】サンプル【書いてみた】 ポリシークラス - Policy Class template <typename T, typename CheckingPolicy

    = NoChecking, typename BadPointerPolicy = BadPointerDoNothing> class PointerWrapper { public: PointerWrapper() : _value(nullptr) {} explicit PointerWrapper(T* p) : _value(p) {} // 暗黙的呼び出しを禁止 operator T*() { if (!CheckingPolicy::CheckPointer(_value)) { return BadPointerPolicy::HandleBadPointer(_value); } else { return _value; } } private: T* _value ; };
  29. 【C++】サンプル【書いてみた】 ポリシークラス - Policy Class PointerWrapper<int> num1(new int); *num1 =

    42; std::cout << "num1:" << *num1 << std::endl; // これは実行時にNULLアクセスによるエラーになる。 //PointerWrapper<int> num2; //*num2 = 42; //std::cout << "num2:" << *num2 << std::endl; // これもエラーになるが、NULLアクセス前に pointer is moldy と出力する。 //PointerWrapper<int, NullChecking> num3; //*num3 = 42; //std::cout << "num3:" << *num3 << std::endl;
  30. 【C++】サンプル【書いてみた】 型推論 // イテレータの型は複雑 std::vector<int>::iterator it = vector.begin(); ↓ //

    このような形で型判別をコンパイラ任せることで、可読性を上げ、冗長性を省く auto it = vector.begin(); // また、decltypeもひとつの選択肢として用意されている。 int foo; decltype(foo) bar = 1; // fooと同じ型でコンパイル時に決定される
  31. 【C++】サンプル【書いてみた】 NULLポインタ void foo(char *); void foo(int); foo(NULL); // foo(int)

    が呼ばれる! 定数 0 には整数定数と NULL ポインタという二つの側面がある。 これまで NULL はプリプロセッサマクロで 0 となるように定義されてきた。 しかし 0 はあらゆるポインタ型への変換が許されているため、オーバーロードの使い方によっては 意図した動作をしないことがある。 そのため、新たな予約語として nullptr が用意された。 nullptr は整数型との比較や代入は禁止されているが、NULL と同様にあらゆるポインタ型への 変換が許されている。 今後、NULL が非推奨となる可能性もあるので、nullptr を使うように心掛けたい。
  32. 【C++】サンプル【書いてみた】 ラムダ式 // 関数オブジェクトと同じように使える void* ptr = NULL; auto isNullptr

    = [](void* ptr) { return ptr == nullptr; }; if (isNullptr(ptr)) { std::cout << "ptr is null." << std::endl; } // その場で呼び出すこともできる int square2 = [](int x) { return x * x; } (2); std::cout << square2 << std::endl; // 4 // Hello,Worldをこんな書き方することも可能。特にこうする意味は無いけど。 []() { std::cout << "Hello,World" << std::endl; } ();
  33. 【C++】サンプル【書いてみた】 ラムダ式を使ってクロージャ int sum = 0; std::vector<int> list; list.push_back(1); list.push_back(2);

    list.push_back(3); std::for_each(list.begin(), list.end(), [&sum](int x) { sum += x; // このラムダ式が宣言されているスコープの変数にアクセス可能 }); std::cout << sum<< std::endl; // 6