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

145

Click here to load reader

description

Sapporo.cpp & CLR/H 合同勉強会 ( http://atnd.org/events/33614 ) で発表したスライドです。

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

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

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

@hotwatermorning

Sapporo.cpp&CLR/H 合同勉強会

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

自己紹介•@hotwatermorning

• Sapporo.cpp

•DTMer

• 7/7のプロ生勉強会で発表など

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

• 「C++のポインタのイメージ」

サラリーマン100人に聞きました。

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

サラリーマン100人に聞きました。

• 「C++のポインタのイメージ」よく分からない

難しい触りたくないトラウマ

俺が規格書だ

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

ちょうどC, C++のポインタを学んでいる人や

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

ポインタ周りの構文で嵌っている人向け

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

本日の訓練メニュー•第一部 「ポインタの基礎」

•第二部「ポインタの嵌りどころ」

•第三部「ポインタを使わない」

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

ポインタの基礎

第一部

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

ポインタの基礎

•ポインタとは、何らかのオブジェクトを指すオブジェクト

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

ポインタの基礎

•ポインタとは、何らかのオブジェクトを指すオブジェクト

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

ポインタの基礎

•オブジェクトとは、

変数の定義や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) より。

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

int main(){ int i;}

この時、変数iはint型のオブジェクト。たとえば変数iはメモリ上で0x7fff5fbfea54から始まる数バイトの領域を占有している。(アドレスは実行毎に変わりうる)

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

この占有しているサイズは型ごとに固有で、sizeof(type)で取得できる。intが4バイトの処理系では、sizeof(int)は4を返し、変数iは0x7fff5fbfea54から始まる4バイトの領域を占有する。

int main(){ int i;}

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

この占有しているサイズは型ごとに固有で、sizeof(type)で取得できる。intが4バイトの処理系では、sizeof(int)は4を返し、変数iは0x7fff5fbfea54から始まる4バイトの領域を占有する。

int main(){ int i;}

・・・0x7fff5fbfea4f0x7fff5fbfea500x7fff5fbfea510x7fff5fbfea520x7fff5fbfea530x7fff5fbfea540x7fff5fbfea550x7fff5fbfea560x7fff5fbfea570x7fff5fbfea580x7fff5fbfea590x7fff5fbfea5a0x7fff5fbfea5b0x7fff5fbfea5c0x7fff5fbfea5d0x7fff5fbfea5e・・・

iiii

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

このプログラムはメモリ上の0x7fff5fbfea54にint型の3を書き込んでいる

int main(){ int i; i = 3;}

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

変数iの占有する領域(0x7fff5fbfea54)を知っているのは変数iだけ。つまり、0x7fff5fbfea54から始まる4バイトに対する値の読み書きは、今のところ変数i経由でしか行えない。

int main(){ int i; i = 3;}

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

この時、変数iからオブジェクトのアドレス(0x7fff5fbfea54)が取得でき、アドレスの先にあるオブジェクトに対して、何バイトかの値を直接読み書きできるような仕組みがあれば・・・

int main(){ int i; i = 3;}

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}

擬似コード

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}

元になる変数を使わずに、アドレス経由でメモリを読み書きして、オブジェクトを操作できる。

擬似コード

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

ポインタの基礎

•ポインタはオブジェクトのアドレスを保持するオブジェクト

• ポインタ変数なんて呼ばれたりもする

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

ポインタの基礎

•ポインタはオブジェクトのアドレスを保持するオブジェクト

• アドレスを経由して、アドレスが指すオブジェクトを操作できる

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

ポインタの基礎

• だだし、前ページの擬似コードには欠陥がある。

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}

擬似コード

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

int main(){ char i;//例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}

擬似コード

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

int main(){ std::list<int> i;//例えば変数i... //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}

擬似コード

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

int main(){ MyClass i;//例えば変数i... //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}

擬似コード

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

ポインタの基礎

• 前ページの擬似コードで使用しているAnyPointerはアドレス値を保存するだけ。

• アドレスの先にあるオブジェクトの型は知らない。

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

ポインタの基礎

• なんのオブジェクトに対するアドレスなのか

• 型ごとにその型のアドレスを扱うためのポインタ

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}

擬似コード

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}

擬似コード

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

ポインタの基礎

• int型にはIntPointerのような型

• charにはCharPointerのような型

• MyClassにはMyClassPointer(ry

• これがあれば、アドレスからその先のオブジェクトを操作できる

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}

擬似コード

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}

実際のコード

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

ポインタの基礎• 擬似コードと実際に動くコードでの文法の対応

• IntPointer → int *

• addressof(i) → &i

• indirect(i) → *i

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

ポインタの基礎• 擬似コードでのポインタの型と実際に動くコードでの型の対応

• IntPointer → int *

• CharPointer → char *

• MyClassPointer → MyClass *

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

ポインタの基礎• ポインタの文法

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

ポインタの宣言• T型のオブジェクトへのポインタの変数を宣言する際には、変数を*(indirection演算子)で修飾する

T!*!pt;// T*!pt; T!*pt; T*pt; とも書ける。

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

アドレスの取得• T型の変数tやオブジェクトの左辺値があるとき

で、オブジェクトのアドレスを取得できる

&t;

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

アドレスの取得• T型の変数tやオブジェクトの左辺値があるとき

• 上記のコードで、tに前置している単項演算子&はaddress-of演算子という

&t;

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

アドレスの取得• T型の変数tやオブジェクトの左辺値があるとき

• このように、変数に&演算子を前置した式は、tのアドレスを保持するポインタを返す

&t;

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

ポインタへの代入• T型の変数tとT型のオブジェクトへのポインタptがあるとき、

このようにして、address-of演算子が返すポインタを別のポインタに代入できる

pt = &t;

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

ポインタの間接参照• T型のオブジェクトへのポインタpt

があるとき、

このようにして、アドレスの先にあるオブジェクト(のlvalue)を取得できる

*pt;

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

ポインタの間接参照• そのため、取得したオブジェクトに対して、

このようにして、値を読み書きできる

• (上記のコードは、ptが指す先のオブジェクトに1を加えている。)

*pt = *pt + 1;

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

ポインタの間接参照

• このように、ポインタからそのアドレスの位置にあるオブジェクトを参照する操作を間接参照(indirection)

という。

*pt;

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

ポインタの間接参照

• 上記のコードで、ポインタに前置している単項演算子*はindirection演算子やdereference演算子という

*pt;

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

ポインタの間接参照

• このように、ポインタに*演算子を前置した式は、ptの指すアドレスの位置にあるオブジェクト(のlvalue)を返す。

*pt;

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる。 int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}

再掲

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

メンバアクセスの構文• ポインタでクラスを扱うときは、非ポインタの時とメンバアクセスの構文が異なる。

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

struct Time { int hour; int minutes; int seconds;};

int main(){ Time t; t.hour = 6; t.minutes = 19; t.seconds = 00;}

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

struct Time { int hour; int minutes; int seconds;};

int main(){ Time t; Time *pt = &t; pt->hour = 6; pt->minutes = 19; pt->seconds = 00;}

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

メンバアクセスの構文• オブジェクトから直接メンバにアクセスするときは、operator.(ドット演算子)を使用する

t.access_to_member_;t.invokeMemberFunction();

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

メンバアクセスの構文• ポインタから間接参照してオブジェクトのメンバにアクセスするときは、operator->(アロー演算子)を使用する

pt->access_to_member_;pt->invokeMemberFunction();

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

newとポインタ• C++で、動的にオブジェクトを生成するには、new演算子を使用する。

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

int main(){ int *pi = new int(); *pi = 1; delete pi;}

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

int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}

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

int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}

new-式によるオブジェクトの生成は、まずメモリ上にその型の領域が確保され、次にオブジェクトのコンストラクタが実行され、最後に作成されたオブジェクトへのポインタが返る。

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

int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}

先程までの例では変数iなどが、0x7fff5fbfea54のようなアドレスにあるオブジェクトを直接表していたために、変数iによって、0x7fff5fbfea54

の領域を直接読み書きできた。

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

int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}

しかし、new-式によって生成されたオブジェクトは、メモリ上にオブジェクトの領域は確保されても、直接そのオブジェクトを指す変数はない。

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

int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}

そのため、new-式から返るポインタ経由で間接的に、オブジェクトを扱うことになる。

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

int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}

また、new-式によって確保されたメモリ領域は明示的に解放しない限り、プログラムが終了するまでメモリ上に残り続ける。使用する必要がなくなった段階でdelete演算子にポインタを渡して解放する必要がある。

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

ポインタの嵌りどころ

第二部

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

ポインタの嵌りどころ

• 構文がややこしい

• 多重ポインタ

• constの付加

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

• 構文がややこしい

• 多重ポインタ

• constの付加

ポインタの嵌りどころ

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

int main(){! int i = 3;! int *pi = &i;

*pi = *pi + 1;}

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

int main(){! int i = 3;! int *pi = &i;

*pi = *pi + 1;}

色々なところに*piが現れてる!!

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

int main(){! int i = 3; int *pi = &i;

*pi = *pi + 1;}

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

宣言時の構文と変数• 宣言時に、これから宣言するオブジェクトがポインタだと指定するために*演算子を使用する。

•変数自体はあくまでint *pi;

pi;

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

int main(){! int i = 3; int *pi = &i;

*pi = *pi + 1;}

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

int main(){! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書くと *pi = *pi + 1;}

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

int main(){! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書ける *pi = *pi + 1;}

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

int main(){! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書ける *pi = *pi + 1;}

間接参照(元のオブエジェクト: 変数iを取得する)

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

• 構文がややこしい

• 多重ポインタ

• constの付加

ポインタの嵌りどころ

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

int main(){ int ** ppi;}

このpは*演算子が2つ指定されている。これはどんなオブジェクトか。次のように書きなおしてみる。

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

int main(){ typedef int * IntPointer; IntPointer * ppi;}

ppiの型はIntPointer型のオブジェクトへのポインタだとわかる。ということは、変数ppiは、IntPointer型(= int *型)のオブジェクトのアドレスを保持できるということ。

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

int main(){ int *pi; int **ppi;

ppi = &pi; //int *型の変数のアドレスを //ppiに代入できる。}

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

int main(){ int **ppi = get_some_pointer();! int *pi = *ppi;! //間接参照するとアドレスの先の! //int *型のオブジェクトが返る! int i = *pi;! //もう一度間接参照するとアドレスの先の! //int型のオブジェクトが返る! int i = **ppi; //2重に間接参照できる}

注) ppi, *ppiに有効なオブジェクトへのアドレスが代入されていなければ上記のコードは未定義動作を起こし、アクセス違反などでクラッシュする。

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

• 構文がややこしい

• 多重ポインタ

• constの付加

ポインタの嵌りどころ

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

まずconstについて• 変数をreadonlyにする仕組み

int main(){ int const value = get_some_value(); //型にconstを後置する value = 0; //コンパイルエラー}

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

まずconstについて• 変数をreadonlyにする仕組み

int main(){ const int value = get_some_value(); //constを型に前置する流儀もある value = 0; //コンパイルエラー}

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

ポインタとconst

• ポインタのconst性には2種類の状況がある

• ポインタというオブジェクト自体のconst性

• ポインタが指すオブジェクトへのconst性

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

• ポインタというオブジェクト自体のconst性

int main(){! int i = 3;! int j = 3;! int * const pi = &i; //piに変数iのアドレスを設定! pi = &j; //コンパイルエラー。! //piの値は変更できない。}

ポインタとconst

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

• ポインタが指すオブジェクトへのconst性

int main(){! int i = 3;!! int const * pi = &i;! *pi = 4; //コンパイルエラー。! //constなオブジェクトは変更できない。}

ポインタとconst

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

• この2つの状況を考慮すると、

この4種類のconst性が異なるポインタが宣言できる。

T * p;T * const p;T const * p;T const * const p;

ポインタとconst

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

• ここでT型のポインタとconst性を

上記のようなtypedefした名前で考えてみると

typedef T * TPointer;typedef T const TConst;typedef TConst * TConstPointer;

ポインタとconst

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

• 宣言はこのようになるTPointer p;TConstPointer p;TPointer const p;TConstPointer const p;

ポインタとconst

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

• 変数pはTPointer型のオブジェクト

• p自体はconstなオブジェクトではない。よって、pの値(保持するアドレス)は変更できる。

• *pで取得されるオブジェクトの型は、TPointer(= T *)の間接参照なのでT。よって、*pで取得できるオブジェクトの値は変更できる。

TPointer p;

ポインタとconst

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

• 変数pはTConstPointer型のオブジェクト

• p自体はconstなオブジェクトではない。よって、pの値(保持するアドレス)は変更できる。

• *pで取得されるオブジェクトの型は、TConstPointer(= T const *)の間接参照なのでTConst。よって、*pで取得できるオブジェクトの値は変更できない。

TConstPointer p;

ポインタとconst

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

• 変数pはTPointer型のconstなオブジェクト

• p自体はconstなオブジェクトである。よって、pの値(保持するアドレス)は変更できない。

• *pで取得されるオブジェクトの型は、TPointer(= T *)の間接参照なのでT。よって、*pで取得できるオブジェクトの値は変更できる。

TPointer const p;

ポインタとconst

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

• 変数pはTConstPointer型のconstなオブジェクト

• p自体はconstなオブジェクトである。よって、pの値(保持するアドレス)は変更できない。

• *pで取得されるオブジェクトの型は、TConstPointer(= T const *)の間接参照なのでTConst。よって、*pで取得できるオブジェクトの値は変更できない。

TConstPointer const p;

ポインタとconst

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

ポインタとconst

• ポインタのconst性まとめ

• ポインタ自体がconstか

• ポインタの指すオブジェクトがconstか

• この二つの組み合わせ

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

ポインタを使わない

第三部

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

ポインタの問題点• 自由に制御でき過ぎる• 容易に無効な状態にできる• 指しているオブジェクトを管理していない

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

ポインタの問題点• 自由に制御でき過ぎる• 容易に無効な状態にできる• 指しているオブジェクトを管理していない

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

//整数の除算をおこなう関数void divide( int dividend, int divisor, int *quotient, int *remainder ){ *quotient = dividend / divisor; *remainder = dividend % divisor;}

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

int main(){ int quotient;

//余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, &quotient, nullptr);

std::cout! << "8 / 4 = " << quotient! << std::endl;}

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

int main(){ int quotient;

//余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, &quotient, nullptr);

std::cout! << "8 / 4 = " << quotient! << std::endl;}

quotientとremainder両方とも指定して欲しいのに、利用者がnullptrや無効なアドレスを渡せてしまう。

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

int main(){ int quotient;

//余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, &quotient, nullptr);

std::cout! << "8 / 4 = " << quotient! << std::endl;}

関数divideの中でnullptrへの書き込みが発生し、プログラムがクラッシュする。

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

参照• C++で導入された、無効値を取らずに何らかのオブジェクトを指す仕組み。

• ポインタ的な性質を持ちつつ、普通の変数のような構文で使用できる。

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

参照• ポインタよりもできることが制限されているため、より安全に使用できる。

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

int main(){ int i = 1; int &ri = i; //参照の定義と初期化 //参照は必ずなんらかの //参照元となるオブジェクトを指定して //初期化されなければならない。 ri = 2; //参照への代入 assert(i == 2);}

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

int main(){ int i = 1; int &ri = i; //参照の定義と初期化 //参照は必ずなんらかの //参照元となるオブジェクトを指定して //初期化されなければならない。 ri = 2; //参照への代入 assert(i == 2);}

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

//整数の除算をおこなう関数//引数の型を参照にしたvoid divide( int dividend, int divisor, int &quotient, int &remainder ){ quotient = dividend / divisor; remainder = dividend % divisor; //参照変数へは普通の変数と同じように //アクセスできる。}

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

int main(){ int quotient; int remainder; //参照引数を取る関数の呼び出し時に //オブジェクトを渡す。 divide(8, 4, quotient, remainder);

std::cout! << "8 / 4 = " << quotient! << std::endl;}

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

int main(){ int quotient; int remainder; //nullptrのような無効なオブジェクトは //表現できないようになっている //以下はコンパイルエラー divide(8, 4, quotient, nullptr);

std::cout! << "8 / 4 = " << quotient! << std::endl;}

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

ポインタの参照• ポインタもアドレスを保持するためのただのオブジェクトなので、ポインタの参照というものも考えられる。

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

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);}

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

ポインタの参照• 関数の引数にポインタの参照を受け渡した時の働きは、C#で言うところのrefキーワードを使用したオブジェクトの受け渡しに近い。

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

ポインタの問題点• 自由に制御でき過ぎる• 容易に無効な状態にできる• 指しているオブジェクトを管理していない

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

オブジェクトの所有権• ポインタはアドレスを保持しているだけで、その先にあるオブジェクトの寿命は管理していない。

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

オブジェクトの所有権• ダングリングポインタ• メモリリーク

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

ダングリングポインタ• オブジェクトが破棄されたのにポインタのアドレスがそのまま

• そのポインタを間接参照すると• 無効なオブジェクトへのアクセスとなる

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

void foo(){ Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 d->getSomeAnotherValue();! //僕「最後にあれをやっておこう」}

一度解放した領域を間接参照しようとした

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

void foo(){ Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 delete d; //僕「関数から抜けるので! //ちゃんと後片付けしておこう」}一度解放した領域をもう一度解放しようとした

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

void foo(){ Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 delete d; //僕「関数から抜けるので! //ちゃんと後片付けしておこう」}一度解放した領域をもう一度解放しようとした

ダングリングポインタ

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

メモリーリーク• newで動的にメモリを確保し生成したオブジェクトのアドレスを紛失し、解放できなくなってしまうこと。

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

void foo(){ Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。}

このまま関数を抜けてしまうと、dのアドレスは誰も知らなくなる。dのアドレスの位置にあるオブジェクトはどうなる?

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

void foo(){ Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。}

C++にはGCが無いため、newで確保されたメモリ領域は、正しく解放しなければ、プログラム終了時までメモリ上に残ってしまう。

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

void foo(){ Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。}

C++にはGCが無いため、newで確保されたメモリ領域は、正しく解放しなければ、プログラム終了時までメモリ上に残ってしまう。

メモリーリーク

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

ポインタでメモリ管理• C言語では、free後にポインタを

NULLで初期化して、ポインタが無効であることを明示するのが一般的なメモリ管理の手法。

• プログラマが常に明示的にポインタの有効性を意識する

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

ポインタでメモリ管理

•一方C++はスマートポインタを使った

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

スマートポインタ• ポインタをラップしつつ、あたかもポインタのように扱えるようにしたクラス

• ポインタが指すオブジェクトの所有権を適切に管理できる

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

int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); }}

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

int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); }}

std::unique_ptrクラスのコンストラクタに、newで生成したオブジェクトのポインタを渡す。以降は変数dataが、newで生成したオブジェクトを管理する。

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

int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); }}変数dataはポインタのように扱える

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

int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); } //ここでdeleteが呼ばれる}スマートポインタの変数の寿命(この例だとスコープを抜ける時)で自動的に管理しているオブジェクトがdeleteされる。

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

int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); } //ここでdeleteが呼ばれる}

このように、オブジェクトの寿命によってリソース管理を行う手法をRAII(Resource Acquisition Is Initialization)という。

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

スマートポインタ• RAIIによって、newで作成したオブジェクトを変数の寿命として管理できる。

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

スマートポインタ• RAIIによって、newで作成したオブジェクトを変数の寿命として管理できる。

• 例外安全性も高まる。• Exceptional C++

• 「例外安全入門」

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

int main(){ std::unique_ptr<int> pi(new int());

*pi = 1;

pi.reset(new int()); //新しいオブジェクトをセット //先に管理していた方は自動でdeleteされる

pi.reset(); //スマートポインタを初期化! //管理しているオブジェクトは! //自動でdeleteされる}

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

int main(){ std::unique_ptr<int> pi(new int());

*pi = 1;

pi.reset(new int()); //新しいオブジェクトをセット //先に管理していた方は自動でdeleteされる

pi.reset(); //スマートポインタを初期化! //管理しているオブジェクトは! //自動でdeleteされる}

newとdeleteと常に明示的に対応させて管理しなくてもよくなる。

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

スマートポインタ• 生のポインタより高機能• カスタムデリータ• オブジェクトが破棄されるときに行われる処理を指定できる。

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

スマートポインタ• 生のポインタより高機能• スマートポインタはその種類ごとに異なる特徴

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

スマートポインタ• std::unique_ptr

• コピー不可/ムーブ可

• newで作成したオブジェクトはただひとつのunique_ptrクラスのインスタンスだけで管理される

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

スマートポインタ• std::shared_ptr

• コピー可/ムーブ可

• newで作成したオブジェクトは複数のshared_ptrのインスタンスから共有される。

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

スマートポインタ• boost::scoped_ptr

• コピー不可/ムーブ不可

• newで作成したオブジェクトは一つのscoped_ptrのインスタンスで管理され、そのインスタンスがスコープから抜けるときに破棄される。

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

スマートポインタ• 生のポインタより高機能• スマートポインタはその種類ごとに異なる特徴

• これらを使い分けることで、コードの意味をより明示できる。

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

まとめ• ポインタの構文をおさらい

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}

擬似コード

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}

実際のコード

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

int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}

実際のコード

宣言

address-of

間接参照

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

まとめ• ポインタの構文をおさらい• スマートポインタを使おう。

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

まとめ• ポインタの構文をおさらい• スマートポインタを使おう。• メモリ管理の煩わしさを軽減する。

• コードの意味を明確にする。

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

その他ポインタと絡む話• ポインタと配列• ポインタと関数• メンバ変数のメモリ管理• ポインタと継承

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

最後にお知らせ• 「C++忘年会 2012 in 札幌」

• 12/15日(土)を候補に進めてます

• 興味が有る方は是非ご参加ください!!

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

最後にお知らせ• 「C++忘年会 2012 in 札幌」

• 12/15日(土)を候補に進めてます

• 興味が有る方は是非ご参加ください!!