Se ha denunciado esta presentación.
Se está descargando tu SlideShare. ×

C++ ポインタ ブートキャンプ

Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Cargando en…3
×

Eche un vistazo a continuación

1 de 145 Anuncio

Más Contenido Relacionado

Presentaciones para usted (20)

Similares a C++ ポインタ ブートキャンプ (20)

Anuncio

Más reciente (20)

Anuncio

C++ ポインタ ブートキャンプ

  1. 1. C++ ポインタ ブートキャンプ @hotwatermorning Sapporo.cpp&CLR/H 合同勉強会
  2. 2. 自己紹介 • @hotwatermorning • Sapporo.cpp • DTMer • 7/7のプロ生勉強会で発表など
  3. 3. サラリーマン100人 に聞きました。 • 「C++のポインタのイメージ」
  4. 4. サラリーマン100人 に聞きました。 • 「C++のポインタのイメージ」 よく分からない 難しい 触りたくない トラウマ 俺が規格書だ
  5. 5. ちょうどC, C++の ポインタを 学んでいる人や
  6. 6. ポインタ周りの構文 で嵌っている人向け
  7. 7. 本日の訓練メニュー • 第一部 「ポインタの基礎」 • 第二部「ポインタの嵌りどころ」 • 第三部「ポインタを使わない」
  8. 8. 第一部 ポインタの基礎
  9. 9. ポインタの基礎 • ポインタとは、 何らかのオブジェクトを指す オブジェクト
  10. 10. ポインタの基礎 • ポインタとは、 何らかのオブジェクトを指す オブジェクト
  11. 11. ポインタの基礎 • オブジェクトとは、 変数の定義やnew-式などによって メモリ上に確保され占有された 領域のこと “オブジェクトはdefinition(3.1), new-式(5.3.4), あるいはimplementation(12.2)によって作成される” (1.8/1) “a most derived objectは0ではないサイズを持ち、1byte以上の領域をメモリ上で占有する。” (1.8/5) Working Draft, Standard for Programming Language C++ (N3337) より。
  12. 12. int main() { int i; } この時、変数iはint型のオブジェクト。 たとえば変数iはメモリ上で0x7fff5fbfea54から 始まる数バイトの領域を占有している。 (アドレスは実行毎に変わりうる)
  13. 13. int main() { int i; } この占有しているサイズは型ごとに 固有で、sizeof(type)で取得できる。 intが4バイトの処理系では、 sizeof(int)は4を返し、 変数iは0x7fff5fbfea54から始まる4バイトの領域 を占有する。
  14. 14. ・・・ 0x7fff5fbfea4f int main() 0x7fff5fbfea50 { 0x7fff5fbfea51 int i; 0x7fff5fbfea52 } 0x7fff5fbfea53 0x7fff5fbfea54 この占有しているサイズは型ごとに 0x7fff5fbfea55 i 0x7fff5fbfea56 固有で、sizeof(type)で取得できる。 0x7fff5fbfea57 intが4バイトの処理系では、 0x7fff5fbfea58 0x7fff5fbfea59 sizeof(int)は4を返し、 0x7fff5fbfea5a 0x7fff5fbfea5b 変数iは0x7fff5fbfea54から始まる4バイトの領域 0x7fff5fbfea5c 0x7fff5fbfea5d を占有する。 0x7fff5fbfea5e ・・・
  15. 15. int main() { int i; i = 3; } このプログラムはメモリ上の0x7fff5fbfea54に int型の3を書き込んでいる
  16. 16. int main() { int i; i = 3; } 変数iの占有する領域(0x7fff5fbfea54)を知ってい るのは変数iだけ。つまり、0x7fff5fbfea54から 始まる4バイトに対する値の読み書きは、 今のところ変数i経由でしか行えない。
  17. 17. int main() { int i; i = 3; } この時、変数iからオブジェクトのアドレス (0x7fff5fbfea54)が取得でき、 アドレスの先にあるオブジェクトに対して、 何バイトかの値を直接読み書きできるような 仕組みがあれば・・・
  18. 18. 擬似コード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるオブジェクトを操作する! ! assert(i == 3): }
  19. 19. 擬似コード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で 元になる変数を使わずに、 ! //その位置にあるオブジェクトを操作する! アドレス経由でメモリを読み書きして、 ! assert(i == 3): } オブジェクトを操作できる。
  20. 20. ポインタの基礎 • ポインタは オブジェクトのアドレスを保持す るオブジェクト • ポインタ変数なんて呼ばれたりも する
  21. 21. ポインタの基礎 • ポインタは オブジェクトのアドレスを保持す るオブジェクト • アドレスを経由して、アドレスが 指すオブジェクトを操作できる
  22. 22. ポインタの基礎 • だだし、前ページの擬似コードに は欠陥がある。
  23. 23. 擬似コード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるオブジェクトを操作する! ! assert(i == 3): }
  24. 24. 擬似コード int main() { char i;//例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるオブジェクトを操作する! ! assert(i == 3): }
  25. 25. 擬似コード int main() { std::list<int> i;//例えば変数i... //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるオブジェクトを操作する! ! assert(i == 3): }
  26. 26. 擬似コード int main() { MyClass i;//例えば変数i... //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるオブジェクトを操作する! ! assert(i == 3): }
  27. 27. ポインタの基礎 • 前ページの擬似コードで使用して いるAnyPointerはアドレス値を保存 するだけ。 • アドレスの先にあるオブジェクト の型は知らない。
  28. 28. ポインタの基礎 • なんのオブジェクトに対する アドレスなのか • 型ごとにその型のアドレスを扱う ためのポインタ
  29. 29. 擬似コード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるオブジェクトを操作する! ! assert(i == 3): }
  30. 30. 擬似コード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるint型のオブジェクトを //操作する! ! assert(i == 3): }
  31. 31. ポインタの基礎 • int型にはIntPointerのような型 • charにはCharPointerのような型 • MyClassにはMyClassPointer(ry • これがあれば、アドレスからその 先のオブジェクトを操作できる
  32. 32. 擬似コード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるint型のオブジェクトを //操作する! ! assert(i == 3): }
  33. 33. 実際のコード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;! ! //piの値は0x7fff5fbfea54 *pi = 3; ! //piに代入されているアドレス経由で ! //その位置にあるint型のオブジェクトを //操作する! ! assert(i == 3): }
  34. 34. ポインタの基礎 • 擬似コードと実際に動く コードでの文法の対応 • IntPointer → int * • addressof(i) → &i • indirect(i) → *i
  35. 35. ポインタの基礎 • 擬似コードでのポインタの型と 実際に動くコードでの型の対応 • IntPointer → int * • CharPointer → char * • MyClassPointer → MyClass *
  36. 36. ポインタの基礎 • ポインタの文法
  37. 37. ポインタの宣言 • T型のオブジェクトへのポインタの 変数を宣言する際には、 変数を*(indirection演算子)で修飾す る T!*!pt; // T*!pt; T!*pt; T*pt; とも書ける。
  38. 38. アドレスの取得 • T型の変数tやオブジェクトの左辺 値があるとき &t; で、オブジェクトのアドレスを 取得できる
  39. 39. アドレスの取得 • T型の変数tやオブジェクトの左辺 値があるとき &t; • 上記のコードで、tに前置している 単項演算子&はaddress-of演算子と いう
  40. 40. アドレスの取得 • T型の変数tやオブジェクトの左辺 値があるとき &t; • このように、変数に&演算子を 前置した式は、tのアドレスを保持 するポインタを返す
  41. 41. ポインタへの代入 • T型の変数tとT型のオブジェクト へのポインタptがあるとき、 pt = &t; このようにして、address-of演算子 が返すポインタを別のポインタに 代入できる
  42. 42. ポインタの間接参照 • T型のオブジェクトへのポインタpt があるとき、 *pt; このようにして、アドレスの先に あるオブジェクト(のlvalue)を取得 できる
  43. 43. ポインタの間接参照 • そのため、取得したオブジェクト に対して、 *pt = *pt + 1; このようにして、値を読み書きで きる • (上記のコードは、ptが指す先のオブジェクトに1を加えている。)
  44. 44. ポインタの間接参照 *pt; • このように、ポインタからその アドレスの位置にある オブジェクトを参照する操作を 間接参照(indirection) という。
  45. 45. ポインタの間接参照 *pt; • 上記のコードで、ポインタに前置 している単項演算子*は indirection演算子や dereference演算子という
  46. 46. ポインタの間接参照 *pt; • このように、ポインタに*演算子を 前置した式は、ptの指すアドレス の位置にあるオブジェクト (のlvalue)を返す。
  47. 47. 再掲 int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる。 int * pi = &i;! ! //piの値は0x7fff5fbfea54 *pi = 3; ! //piに代入されているアドレス経由で ! //その位置にあるint型のオブジェクトを //操作する! ! assert(i == 3): }
  48. 48. メンバアクセスの構文 • ポインタでクラスを扱うときは、 非ポインタの時とメンバアクセス の構文が異なる。
  49. 49. struct Time { int hour; int minutes; int seconds; }; int main() { Time t; t.hour = 6; t.minutes = 19; t.seconds = 00; }
  50. 50. struct Time { int hour; int minutes; int seconds; }; int main() { Time t; Time *pt = &t; pt->hour = 6; pt->minutes = 19; pt->seconds = 00; }
  51. 51. メンバアクセスの構文 • オブジェクトから直接メンバに アクセスするときは、 operator.(ドット演算子)を使用する t.access_to_member_; t.invokeMemberFunction();
  52. 52. メンバアクセスの構文 • ポインタから間接参照して オブジェクトのメンバにアクセス するときは、operator->(アロー演 算子)を使用する pt->access_to_member_; pt->invokeMemberFunction();
  53. 53. newとポインタ • C++で、動的にオブジェクトを生 成するには、new演算子を使用す る。
  54. 54. int main() { int *pi = new int(); *pi = 1; delete pi; }
  55. 55. int main() { MyClass *pm = new MyClass(); *pm = 1; delete pm; }
  56. 56. int main() { MyClass *pm = new MyClass(); *pm = 1; delete pm; } new-式によるオブジェクトの生成は、 まずメモリ上にその型の領域が確保され、 次にオブジェクトのコンストラクタが 実行され、最後に作成されたオブジェクトへの ポインタが返る。
  57. 57. int main() { MyClass *pm = new MyClass(); *pm = 1; delete pm; } 先程までの例では変数iなどが、0x7fff5fbfea54の ようなアドレスにあるオブジェクトを直接 表していたために、変数iによって、0x7fff5fbfea54 の領域を直接読み書きできた。
  58. 58. int main() { MyClass *pm = new MyClass(); *pm = 1; delete pm; } しかし、new-式によって生成された オブジェクトは、メモリ上にオブジェクトの 領域は確保されても、直接そのオブジェクトを 指す変数はない。
  59. 59. int main() { MyClass *pm = new MyClass(); *pm = 1; delete pm; } そのため、new-式から返るポインタ経由で 間接的に、オブジェクトを扱うことになる。
  60. 60. int main() { MyClass *pm = new MyClass(); *pm = 1; delete pm; } また、new-式によって確保されたメモリ領域は 明示的に解放しない限り、プログラムが終了 するまでメモリ上に残り続ける。 使用する必要がなくなった段階でdelete演算子に ポインタを渡して解放する必要がある。
  61. 61. 第二部 ポインタの嵌りどころ
  62. 62. ポインタの嵌りどころ • 構文がややこしい • 多重ポインタ • constの付加
  63. 63. ポインタの嵌りどころ • 構文がややこしい • 多重ポインタ • constの付加
  64. 64. int main() { ! int i = 3; ! int *pi = &i; *pi = *pi + 1; }
  65. 65. int main() { ! int i = 3; ! int *pi = &i; *pi = *pi + 1; } 色々なところに*piが現れてる!!
  66. 66. int main() { ! int i = 3; int *pi = &i; *pi = *pi + 1; }
  67. 67. 宣言時の構文と変数 • 宣言時に、これから宣言するオブ ジェクトがポインタだと指定する ために*演算子を使用する。 int *pi; • 変数自体はあくまで pi;
  68. 68. int main() { ! int i = 3; int *pi = &i; *pi = *pi + 1; }
  69. 69. int main() { ! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書くと *pi = *pi + 1; }
  70. 70. int main() { ! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書ける *pi = *pi + 1; }
  71. 71. int main() { ! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書ける *pi = *pi + 1; } 間接参照 (元のオブエジェクト: 変数iを取得する)
  72. 72. ポインタの嵌りどころ • 構文がややこしい • 多重ポインタ • constの付加
  73. 73. int main() { int ** ppi; } このpは*演算子が2つ指定されている。 これはどんなオブジェクトか。 次のように書きなおしてみる。
  74. 74. int main() { typedef int * IntPointer; IntPointer * ppi; } ppiの型はIntPointer型のオブジェクトへの ポインタだとわかる。 ということは、変数ppiは、 IntPointer型(= int *型)のオブジェクトのアドレス を保持できるということ。
  75. 75. int main() { int *pi; int **ppi; ppi = &pi; //int *型の変数のアドレスを //ppiに代入できる。 }
  76. 76. int main() { int **ppi = get_some_pointer(); ! int *pi = *ppi; ! //間接参照するとアドレスの先の ! //int *型のオブジェクトが返る ! int i = *pi; ! //もう一度間接参照するとアドレスの先の ! //int型のオブジェクトが返る ! int i = **ppi; //2重に間接参照できる } 注) ppi, *ppiに有効なオブジェクトへのアドレスが代入されていなければ 上記のコードは未定義動作を起こし、アクセス違反などでクラッシュする。
  77. 77. ポインタの嵌りどころ • 構文がややこしい • 多重ポインタ • constの付加
  78. 78. まずconstについて • 変数をreadonlyにする仕組み int main() { int const value = get_some_value(); //型にconstを後置する value = 0; //コンパイルエラー }
  79. 79. まずconstについて • 変数をreadonlyにする仕組み int main() { const int value = get_some_value(); //constを型に前置する流儀もある value = 0; //コンパイルエラー }
  80. 80. ポインタとconst • ポインタのconst性には2種類の 状況がある • ポインタというオブジェクト 自体のconst性 • ポインタが指すオブジェクト へのconst性
  81. 81. ポインタとconst • ポインタというオブジェクト 自体のconst性 int main() { ! int i = 3; ! int j = 3; ! int * const pi = &i; //piに変数iのアドレスを設定 ! pi = &j; //コンパイルエラー。 ! //piの値は変更できない。 }
  82. 82. ポインタとconst • ポインタが指すオブジェクト へのconst性 int main() { ! int i = 3;! ! int const * pi = &i; ! *pi = 4; //コンパイルエラー。 ! //constなオブジェクトは変更できない。 }
  83. 83. ポインタとconst • この2つの状況を考慮すると、 T * p; T * const p; T const * p; T const * const p; この4種類のconst性が異なる ポインタが宣言できる。
  84. 84. ポインタとconst • ここでT型のポインタとconst性を typedef T * TPointer; typedef T const TConst; typedef TConst * TConstPointer; 上記のようなtypedefした名前で考 えてみると
  85. 85. ポインタとconst • 宣言はこのようになる TPointer p; TConstPointer p; TPointer const p; TConstPointer const p;
  86. 86. ポインタとconst TPointer p; • 変数pはTPointer型のオブジェクト • p自体はconstなオブジェクトではない。 よって、pの値(保持するアドレス)は変更で きる。 • *pで取得されるオブジェクトの型は、 TPointer(= T *)の間接参照なのでT。 よって、*pで取得できるオブジェクトの値は 変更できる。
  87. 87. ポインタとconst TConstPointer p; • 変数pはTConstPointer型のオブジェクト • p自体はconstなオブジェクトではない。 よって、pの値(保持するアドレス)は変更で きる。 • *pで取得されるオブジェクトの型は、 TConstPointer(= T const *)の間接参照なので TConst。よって、*pで取得できるオブジェク トの値は変更できない。
  88. 88. ポインタとconst TPointer const p; • 変数pはTPointer型のconstなオブジェクト • p自体はconstなオブジェクトである。 よって、pの値(保持するアドレス)は変更で きない。 • *pで取得されるオブジェクトの型は、 TPointer(= T *)の間接参照なのでT。 よって、*pで取得できるオブジェクトの値は 変更できる。
  89. 89. ポインタとconst TConstPointer const p; • 変数pはTConstPointer型のconstなオブジェクト • p自体はconstなオブジェクトである。 よって、pの値(保持するアドレス)は変更で きない。 • *pで取得されるオブジェクトの型は、 TConstPointer(= T const *)の間接参照なので TConst。よって、*pで取得できるオブジェク トの値は変更できない。
  90. 90. ポインタとconst • ポインタのconst性まとめ • ポインタ自体がconstか • ポインタの指すオブジェクトが constか • この二つの組み合わせ
  91. 91. 第三部 ポインタを使わない
  92. 92. ポインタの問題点 • 自由に制御でき過ぎる • 容易に無効な状態にできる • 指しているオブジェクトを管理 していない
  93. 93. ポインタの問題点 • 自由に制御でき過ぎる • 容易に無効な状態にできる • 指しているオブジェクトを管理 していない
  94. 94. //整数の除算をおこなう関数 void divide( int dividend, int divisor, int *quotient, int *remainder ) { *quotient = dividend / divisor; *remainder = dividend % divisor; }
  95. 95. int main() { int quotient; //余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, &quotient, nullptr); std::cout ! << "8 / 4 = " << quotient ! << std::endl; }
  96. 96. int main() { int quotient; //余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, &quotient, nullptr); quotientとremainder両方とも指定して欲しいのに、 std::cout ! << "8 / 4 = " << quotient 利用者がnullptrや無効なアドレスを渡せてしまう。 ! << std::endl; }
  97. 97. int main() { int quotient; //余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, &quotient, nullptr); 関数divideの中でnullptrへの書き込みが std::cout ! << "8 / 4 = " << quotient 発生し、プログラムがクラッシュする。 ! << std::endl; }
  98. 98. 参照 • C++で導入された、無効値を 取らずに何らかのオブジェクト を指す仕組み。 • ポインタ的な性質を持ちつつ、 普通の変数のような構文で使用 できる。
  99. 99. 参照 • ポインタよりもできることが 制限されているため、より安全 に使用できる。
  100. 100. int main() { int i = 1; int &ri = i; //参照の定義と初期化 //参照は必ずなんらかの //参照元となるオブジェクトを指定して //初期化されなければならない。 ri = 2; //参照への代入 assert(i == 2); }
  101. 101. int main() { int i = 1; int &ri = i; //参照の定義と初期化 //参照は必ずなんらかの //参照元となるオブジェクトを指定して //初期化されなければならない。 ri = 2; //参照への代入 assert(i == 2); }
  102. 102. //整数の除算をおこなう関数 //引数の型を参照にした void divide( int dividend, int divisor, int &quotient, int &remainder ) { quotient = dividend / divisor; remainder = dividend % divisor; //参照変数へは普通の変数と同じように //アクセスできる。 }
  103. 103. int main() { int quotient; int remainder; //参照引数を取る関数の呼び出し時に //オブジェクトを渡す。 divide(8, 4, quotient, remainder); std::cout ! << "8 / 4 = " << quotient ! << std::endl; }
  104. 104. int main() { int quotient; int remainder; //nullptrのような無効なオブジェクトは //表現できないようになっている //以下はコンパイルエラー divide(8, 4, quotient, nullptr); std::cout ! << "8 / 4 = " << quotient ! << std::endl; }
  105. 105. ポインタの参照 • ポインタもアドレスを保持する ためのただのオブジェクトなの で、ポインタの参照というもの も考えられる。
  106. 106. void delete_and_set_null(int *& i) { delete i; i = nullptr; } int main() { int *pi = new int (); *pi = 1; delete_and_set_null(pi); ! //関数にポインタを参照で渡して、 ! //値をdeleteしたあとヌルポインタを代入 ! assert(pi == nullptr); }
  107. 107. ポインタの参照 • 関数の引数にポインタの参照を 受け渡した時の働きは、C#で 言うところのrefキーワードを 使用したオブジェクトの受け 渡しに近い。
  108. 108. ポインタの問題点 • 自由に制御でき過ぎる • 容易に無効な状態にできる • 指しているオブジェクトを管理 していない
  109. 109. オブジェクトの所有権 • ポインタはアドレスを保持して いるだけで、その先にあるオブ ジェクトの寿命は管理していな い。
  110. 110. オブジェクトの所有権 • ダングリングポインタ • メモリリーク
  111. 111. ダングリングポインタ • オブジェクトが破棄されたのに ポインタのアドレスがそのまま • そのポインタを間接参照すると • 無効なオブジェクトへの アクセスとなる
  112. 112. void foo() { Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 d->getSomeAnotherValue(); ! //僕「最後にあれをやっておこう」 } 一度解放した領域を間接参照しようとした
  113. 113. void foo() { Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 delete d; //僕「関数から抜けるので ! //ちゃんと後片付けしておこう」 } 一度解放した領域をもう一度解放しようとした
  114. 114. void foo() { Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 ダングリングポインタ //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 delete d; //僕「関数から抜けるので ! //ちゃんと後片付けしておこう」 } 一度解放した領域をもう一度解放しようとした
  115. 115. メモリーリーク • newで動的にメモリを確保し生成 したオブジェクトのアドレスを 紛失し、解放できなくなってし まうこと。
  116. 116. void foo() { Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。 } このまま関数を抜けてしまうと、dのアドレスは 誰も知らなくなる。 dのアドレスの位置にあるオブジェクトは どうなる?
  117. 117. void foo() { Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。 } C++にはGCが無いため、newで確保されたメモリ 領域は、正しく解放しなければ、 プログラム終了時までメモリ上に残ってしまう。
  118. 118. void foo() { Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。 } メモリーリーク C++にはGCが無いため、newで確保されたメモリ 領域は、正しく解放しなければ、 プログラム終了時までメモリ上に残ってしまう。
  119. 119. ポインタでメモリ管理 • C言語では、free後にポインタを NULLで初期化して、ポインタが 無効であることを明示するのが 一般的なメモリ管理の手法。 • プログラマが常に明示的にポイ ンタの有効性を意識する
  120. 120. ポインタでメモリ管理 • 一方C++は スマートポインタを 使った
  121. 121. スマートポインタ • ポインタをラップしつつ、 あたかもポインタのように扱え るようにしたクラス • ポインタが指すオブジェクトの 所有権を適切に管理できる
  122. 122. int main() { { std::unique_ptr<Data> data( new Data() ); data->doSomething(); } }
  123. 123. int main() { { std::unique_ptr<Data> data( new Data() ); data->doSomething(); std::unique_ptrクラスのコンストラクタに、 } } newで生成したオブジェクトのポインタを渡す。 以降は変数dataが、newで生成した オブジェクトを管理する。
  124. 124. int main() { { std::unique_ptr<Data> data( new Data() ); data->doSomething(); } } 変数dataはポインタのように扱える
  125. 125. int main() { { std::unique_ptr<Data> data( new Data() ); data->doSomething(); } //ここでdeleteが呼ばれる } スマートポインタの変数の寿命(この例だと スコープを抜ける時)で自動的に管理している オブジェクトがdeleteされる。
  126. 126. int main() { { std::unique_ptr<Data> data( new Data() ); data->doSomething(); このように、オブジェクトの寿命によって } //ここでdeleteが呼ばれる リソース管理を行う手法を } RAII(Resource Acquisition Is Initialization)という。
  127. 127. スマートポインタ • RAIIによって、newで作成したオ ブジェクトを変数の寿命として 管理できる。
  128. 128. スマートポインタ • RAIIによって、newで作成したオ ブジェクトを変数の寿命として 管理できる。 • 例外安全性も高まる。 • Exceptional C++ • 「例外安全入門」
  129. 129. int main() { std::unique_ptr<int> pi(new int()); *pi = 1; pi.reset(new int()); //新しいオブジェクトをセット //先に管理していた方は自動でdeleteされる pi.reset(); //スマートポインタを初期化 ! //管理しているオブジェクトは ! //自動でdeleteされる }
  130. 130. int main() { std::unique_ptr<int> pi(new int()); *pi = 1; pi.reset(new int()); //新しいオブジェクトをセット newとdeleteと常に明示的に対応させて管理しな //先に管理していた方は自動でdeleteされる くてもよくなる。 pi.reset(); //スマートポインタを初期化 ! //管理しているオブジェクトは ! //自動でdeleteされる }
  131. 131. スマートポインタ • 生のポインタより高機能 • カスタムデリータ • オブジェクトが破棄されるときに行われ る処理を指定できる。
  132. 132. スマートポインタ • 生のポインタより高機能 • スマートポインタはその種類 ごとに異なる特徴
  133. 133. スマートポインタ • std::unique_ptr • コピー不可/ムーブ可 • newで作成したオブジェクトは ただひとつのunique_ptrクラスのインスタン スだけで管理される
  134. 134. スマートポインタ • std::shared_ptr • コピー可/ムーブ可 • newで作成したオブジェクトは 複数のshared_ptrのインスタンスから共有さ れる。
  135. 135. スマートポインタ • boost::scoped_ptr • コピー不可/ムーブ不可 • newで作成したオブジェクトは 一つのscoped_ptrのインスタンスで管理さ れ、そのインスタンスがスコープから抜け るときに破棄される。
  136. 136. スマートポインタ • 生のポインタより高機能 • スマートポインタはその種類 ごとに異なる特徴 • これらを使い分けることで、 コードの意味をより明示でき る。
  137. 137. まとめ • ポインタの構文をおさらい
  138. 138. 擬似コード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);! ! //piの値は0x7fff5fbfea54 indirect(pi) = 3; ! //piに代入されているアドレス経由で ! //その位置にあるint型のオブジェクトを //操作する! ! assert(i == 3): }
  139. 139. 実際のコード int main() { int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;! ! //piの値は0x7fff5fbfea54 *pi = 3; ! //piに代入されているアドレス経由で ! //その位置にあるint型のオブジェクトを //操作する! ! assert(i == 3): }
  140. 140. 実際のコード int main() { int i; //例えば変数iのオブジェクトは 宣言 //0x7fff5fbfea54にいる int * pi = &i;! ! //piの値は0x7fff5fbfea54 address-of *pi = 3; ! //piに代入されているアドレス経由で 間接参照 ! //その位置にあるint型のオブジェクトを //操作する! ! assert(i == 3): }
  141. 141. まとめ • ポインタの構文をおさらい • スマートポインタを使おう。
  142. 142. まとめ • ポインタの構文をおさらい • スマートポインタを使おう。 • メモリ管理の煩わしさを軽減す る。 • コードの意味を明確にする。
  143. 143. その他ポインタと絡む話 • ポインタと配列 • ポインタと関数 • メンバ変数のメモリ管理 • ポインタと継承
  144. 144. 最後にお知らせ • 「C++忘年会 2012 in 札幌」 • 12/15日(土)を候補に進めてます • 興味が有る方は是非ご参加くださ い!!
  145. 145. 最後にお知らせ • 「C++忘年会 2012 in 札幌」 • 12/15日(土)を候補に進めてます • 興味が有る方は是非ご参加くださ い!!

×