c++ ポインタ ブートキャンプ
DESCRIPTION
Sapporo.cpp & CLR/H 合同勉強会 ( http://atnd.org/events/33614 ) で発表したスライドです。TRANSCRIPT
C++ポインタ ブートキャンプ
@hotwatermorning
Sapporo.cpp&CLR/H 合同勉強会
自己紹介•@hotwatermorning
• Sapporo.cpp
•DTMer
• 7/7のプロ生勉強会で発表など
• 「C++のポインタのイメージ」
サラリーマン100人に聞きました。
サラリーマン100人に聞きました。
• 「C++のポインタのイメージ」よく分からない
難しい触りたくないトラウマ
俺が規格書だ
ちょうどC, 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) より。
int main(){ int i;}
この時、変数iはint型のオブジェクト。たとえば変数iはメモリ上で0x7fff5fbfea54から始まる数バイトの領域を占有している。(アドレスは実行毎に変わりうる)
この占有しているサイズは型ごとに固有で、sizeof(type)で取得できる。intが4バイトの処理系では、sizeof(int)は4を返し、変数iは0x7fff5fbfea54から始まる4バイトの領域を占有する。
int main(){ int i;}
この占有しているサイズは型ごとに固有で、sizeof(type)で取得できる。intが4バイトの処理系では、sizeof(int)は4を返し、変数iは0x7fff5fbfea54から始まる4バイトの領域を占有する。
int main(){ int i;}
・・・0x7fff5fbfea4f0x7fff5fbfea500x7fff5fbfea510x7fff5fbfea520x7fff5fbfea530x7fff5fbfea540x7fff5fbfea550x7fff5fbfea560x7fff5fbfea570x7fff5fbfea580x7fff5fbfea590x7fff5fbfea5a0x7fff5fbfea5b0x7fff5fbfea5c0x7fff5fbfea5d0x7fff5fbfea5e・・・
iiii
このプログラムはメモリ上の0x7fff5fbfea54にint型の3を書き込んでいる
int main(){ int i; i = 3;}
変数iの占有する領域(0x7fff5fbfea54)を知っているのは変数iだけ。つまり、0x7fff5fbfea54から始まる4バイトに対する値の読み書きは、今のところ変数i経由でしか行えない。
int main(){ int i; i = 3;}
この時、変数iからオブジェクトのアドレス(0x7fff5fbfea54)が取得でき、アドレスの先にあるオブジェクトに対して、何バイトかの値を直接読み書きできるような仕組みがあれば・・・
int main(){ int i; i = 3;}
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}
擬似コード
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}
元になる変数を使わずに、アドレス経由でメモリを読み書きして、オブジェクトを操作できる。
擬似コード
ポインタの基礎
•ポインタはオブジェクトのアドレスを保持するオブジェクト
• ポインタ変数なんて呼ばれたりもする
ポインタの基礎
•ポインタはオブジェクトのアドレスを保持するオブジェクト
• アドレスを経由して、アドレスが指すオブジェクトを操作できる
ポインタの基礎
• だだし、前ページの擬似コードには欠陥がある。
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}
擬似コード
int main(){ char i;//例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}
擬似コード
int main(){ std::list<int> i;//例えば変数i... //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}
擬似コード
int main(){ MyClass i;//例えば変数i... //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}
擬似コード
ポインタの基礎
• 前ページの擬似コードで使用しているAnyPointerはアドレス値を保存するだけ。
• アドレスの先にあるオブジェクトの型は知らない。
ポインタの基礎
• なんのオブジェクトに対するアドレスなのか
• 型ごとにその型のアドレスを扱うためのポインタ
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる AnyPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるオブジェクトを操作する!! assert(i == 3):}
擬似コード
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}
擬似コード
ポインタの基礎
• int型にはIntPointerのような型
• charにはCharPointerのような型
• MyClassにはMyClassPointer(ry
• これがあれば、アドレスからその先のオブジェクトを操作できる
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}
擬似コード
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}
実際のコード
ポインタの基礎• 擬似コードと実際に動くコードでの文法の対応
• IntPointer → int *
• addressof(i) → &i
• indirect(i) → *i
ポインタの基礎• 擬似コードでのポインタの型と実際に動くコードでの型の対応
• IntPointer → int *
• CharPointer → char *
• MyClassPointer → MyClass *
ポインタの基礎• ポインタの文法
ポインタの宣言• T型のオブジェクトへのポインタの変数を宣言する際には、変数を*(indirection演算子)で修飾する
T!*!pt;// T*!pt; T!*pt; T*pt; とも書ける。
アドレスの取得• T型の変数tやオブジェクトの左辺値があるとき
で、オブジェクトのアドレスを取得できる
&t;
アドレスの取得• T型の変数tやオブジェクトの左辺値があるとき
• 上記のコードで、tに前置している単項演算子&はaddress-of演算子という
&t;
アドレスの取得• T型の変数tやオブジェクトの左辺値があるとき
• このように、変数に&演算子を前置した式は、tのアドレスを保持するポインタを返す
&t;
ポインタへの代入• T型の変数tとT型のオブジェクトへのポインタptがあるとき、
このようにして、address-of演算子が返すポインタを別のポインタに代入できる
pt = &t;
ポインタの間接参照• T型のオブジェクトへのポインタpt
があるとき、
このようにして、アドレスの先にあるオブジェクト(のlvalue)を取得できる
*pt;
ポインタの間接参照• そのため、取得したオブジェクトに対して、
このようにして、値を読み書きできる
• (上記のコードは、ptが指す先のオブジェクトに1を加えている。)
*pt = *pt + 1;
ポインタの間接参照
• このように、ポインタからそのアドレスの位置にあるオブジェクトを参照する操作を間接参照(indirection)
という。
*pt;
ポインタの間接参照
• 上記のコードで、ポインタに前置している単項演算子*はindirection演算子やdereference演算子という
*pt;
ポインタの間接参照
• このように、ポインタに*演算子を前置した式は、ptの指すアドレスの位置にあるオブジェクト(のlvalue)を返す。
*pt;
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる。 int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}
再掲
メンバアクセスの構文• ポインタでクラスを扱うときは、非ポインタの時とメンバアクセスの構文が異なる。
struct Time { int hour; int minutes; int seconds;};
int main(){ Time t; t.hour = 6; t.minutes = 19; t.seconds = 00;}
struct Time { int hour; int minutes; int seconds;};
int main(){ Time t; Time *pt = &t; pt->hour = 6; pt->minutes = 19; pt->seconds = 00;}
メンバアクセスの構文• オブジェクトから直接メンバにアクセスするときは、operator.(ドット演算子)を使用する
t.access_to_member_;t.invokeMemberFunction();
メンバアクセスの構文• ポインタから間接参照してオブジェクトのメンバにアクセスするときは、operator->(アロー演算子)を使用する
pt->access_to_member_;pt->invokeMemberFunction();
newとポインタ• C++で、動的にオブジェクトを生成するには、new演算子を使用する。
int main(){ int *pi = new int(); *pi = 1; delete pi;}
int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}
int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}
new-式によるオブジェクトの生成は、まずメモリ上にその型の領域が確保され、次にオブジェクトのコンストラクタが実行され、最後に作成されたオブジェクトへのポインタが返る。
int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}
先程までの例では変数iなどが、0x7fff5fbfea54のようなアドレスにあるオブジェクトを直接表していたために、変数iによって、0x7fff5fbfea54
の領域を直接読み書きできた。
int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}
しかし、new-式によって生成されたオブジェクトは、メモリ上にオブジェクトの領域は確保されても、直接そのオブジェクトを指す変数はない。
int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}
そのため、new-式から返るポインタ経由で間接的に、オブジェクトを扱うことになる。
int main(){ MyClass *pm = new MyClass(); *pm = 1; delete pm;}
また、new-式によって確保されたメモリ領域は明示的に解放しない限り、プログラムが終了するまでメモリ上に残り続ける。使用する必要がなくなった段階でdelete演算子にポインタを渡して解放する必要がある。
ポインタの嵌りどころ
第二部
ポインタの嵌りどころ
• 構文がややこしい
• 多重ポインタ
• constの付加
• 構文がややこしい
• 多重ポインタ
• constの付加
ポインタの嵌りどころ
int main(){! int i = 3;! int *pi = &i;
*pi = *pi + 1;}
int main(){! int i = 3;! int *pi = &i;
*pi = *pi + 1;}
色々なところに*piが現れてる!!
int main(){! int i = 3; int *pi = &i;
*pi = *pi + 1;}
宣言時の構文と変数• 宣言時に、これから宣言するオブジェクトがポインタだと指定するために*演算子を使用する。
•変数自体はあくまでint *pi;
pi;
int main(){! int i = 3; int *pi = &i;
*pi = *pi + 1;}
int main(){! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書くと *pi = *pi + 1;}
int main(){! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書ける *pi = *pi + 1;}
int main(){! int i = 3; int *pi; //ポインタの宣言と pi = &i; //代入を分けて書ける *pi = *pi + 1;}
間接参照(元のオブエジェクト: 変数iを取得する)
• 構文がややこしい
• 多重ポインタ
• constの付加
ポインタの嵌りどころ
int main(){ int ** ppi;}
このpは*演算子が2つ指定されている。これはどんなオブジェクトか。次のように書きなおしてみる。
int main(){ typedef int * IntPointer; IntPointer * ppi;}
ppiの型はIntPointer型のオブジェクトへのポインタだとわかる。ということは、変数ppiは、IntPointer型(= int *型)のオブジェクトのアドレスを保持できるということ。
int main(){ int *pi; int **ppi;
ppi = π //int *型の変数のアドレスを //ppiに代入できる。}
int main(){ int **ppi = get_some_pointer();! int *pi = *ppi;! //間接参照するとアドレスの先の! //int *型のオブジェクトが返る! int i = *pi;! //もう一度間接参照するとアドレスの先の! //int型のオブジェクトが返る! int i = **ppi; //2重に間接参照できる}
注) ppi, *ppiに有効なオブジェクトへのアドレスが代入されていなければ上記のコードは未定義動作を起こし、アクセス違反などでクラッシュする。
• 構文がややこしい
• 多重ポインタ
• constの付加
ポインタの嵌りどころ
まずconstについて• 変数をreadonlyにする仕組み
int main(){ int const value = get_some_value(); //型にconstを後置する value = 0; //コンパイルエラー}
まずconstについて• 変数をreadonlyにする仕組み
int main(){ const int value = get_some_value(); //constを型に前置する流儀もある value = 0; //コンパイルエラー}
ポインタとconst
• ポインタのconst性には2種類の状況がある
• ポインタというオブジェクト自体のconst性
• ポインタが指すオブジェクトへのconst性
• ポインタというオブジェクト自体のconst性
int main(){! int i = 3;! int j = 3;! int * const pi = &i; //piに変数iのアドレスを設定! pi = &j; //コンパイルエラー。! //piの値は変更できない。}
ポインタとconst
• ポインタが指すオブジェクトへのconst性
int main(){! int i = 3;!! int const * pi = &i;! *pi = 4; //コンパイルエラー。! //constなオブジェクトは変更できない。}
ポインタとconst
• この2つの状況を考慮すると、
この4種類のconst性が異なるポインタが宣言できる。
T * p;T * const p;T const * p;T const * const p;
ポインタとconst
• ここでT型のポインタとconst性を
上記のようなtypedefした名前で考えてみると
typedef T * TPointer;typedef T const TConst;typedef TConst * TConstPointer;
ポインタとconst
• 宣言はこのようになるTPointer p;TConstPointer p;TPointer const p;TConstPointer const p;
ポインタとconst
• 変数pはTPointer型のオブジェクト
• p自体はconstなオブジェクトではない。よって、pの値(保持するアドレス)は変更できる。
• *pで取得されるオブジェクトの型は、TPointer(= T *)の間接参照なのでT。よって、*pで取得できるオブジェクトの値は変更できる。
TPointer p;
ポインタとconst
• 変数pはTConstPointer型のオブジェクト
• p自体はconstなオブジェクトではない。よって、pの値(保持するアドレス)は変更できる。
• *pで取得されるオブジェクトの型は、TConstPointer(= T const *)の間接参照なのでTConst。よって、*pで取得できるオブジェクトの値は変更できない。
TConstPointer p;
ポインタとconst
• 変数pはTPointer型のconstなオブジェクト
• p自体はconstなオブジェクトである。よって、pの値(保持するアドレス)は変更できない。
• *pで取得されるオブジェクトの型は、TPointer(= T *)の間接参照なのでT。よって、*pで取得できるオブジェクトの値は変更できる。
TPointer const p;
ポインタとconst
• 変数pはTConstPointer型のconstなオブジェクト
• p自体はconstなオブジェクトである。よって、pの値(保持するアドレス)は変更できない。
• *pで取得されるオブジェクトの型は、TConstPointer(= T const *)の間接参照なのでTConst。よって、*pで取得できるオブジェクトの値は変更できない。
TConstPointer const p;
ポインタとconst
ポインタとconst
• ポインタのconst性まとめ
• ポインタ自体がconstか
• ポインタの指すオブジェクトがconstか
• この二つの組み合わせ
ポインタを使わない
第三部
ポインタの問題点• 自由に制御でき過ぎる• 容易に無効な状態にできる• 指しているオブジェクトを管理していない
ポインタの問題点• 自由に制御でき過ぎる• 容易に無効な状態にできる• 指しているオブジェクトを管理していない
//整数の除算をおこなう関数void divide( int dividend, int divisor, int *quotient, int *remainder ){ *quotient = dividend / divisor; *remainder = dividend % divisor;}
int main(){ int quotient;
//余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, "ient, nullptr);
std::cout! << "8 / 4 = " << quotient! << std::endl;}
int main(){ int quotient;
//余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, "ient, nullptr);
std::cout! << "8 / 4 = " << quotient! << std::endl;}
quotientとremainder両方とも指定して欲しいのに、利用者がnullptrや無効なアドレスを渡せてしまう。
int main(){ int quotient;
//余りは使わなくていいから //nullptr指定しちゃえ☆ divide(8, 4, "ient, nullptr);
std::cout! << "8 / 4 = " << quotient! << std::endl;}
関数divideの中でnullptrへの書き込みが発生し、プログラムがクラッシュする。
参照• C++で導入された、無効値を取らずに何らかのオブジェクトを指す仕組み。
• ポインタ的な性質を持ちつつ、普通の変数のような構文で使用できる。
参照• ポインタよりもできることが制限されているため、より安全に使用できる。
int main(){ int i = 1; int &ri = i; //参照の定義と初期化 //参照は必ずなんらかの //参照元となるオブジェクトを指定して //初期化されなければならない。 ri = 2; //参照への代入 assert(i == 2);}
int main(){ int i = 1; int &ri = i; //参照の定義と初期化 //参照は必ずなんらかの //参照元となるオブジェクトを指定して //初期化されなければならない。 ri = 2; //参照への代入 assert(i == 2);}
//整数の除算をおこなう関数//引数の型を参照にしたvoid divide( int dividend, int divisor, int "ient, int &remainder ){ quotient = dividend / divisor; remainder = dividend % divisor; //参照変数へは普通の変数と同じように //アクセスできる。}
int main(){ int quotient; int remainder; //参照引数を取る関数の呼び出し時に //オブジェクトを渡す。 divide(8, 4, quotient, remainder);
std::cout! << "8 / 4 = " << quotient! << std::endl;}
int main(){ int quotient; int remainder; //nullptrのような無効なオブジェクトは //表現できないようになっている //以下はコンパイルエラー divide(8, 4, quotient, nullptr);
std::cout! << "8 / 4 = " << quotient! << std::endl;}
ポインタの参照• ポインタもアドレスを保持するためのただのオブジェクトなので、ポインタの参照というものも考えられる。
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);}
ポインタの参照• 関数の引数にポインタの参照を受け渡した時の働きは、C#で言うところのrefキーワードを使用したオブジェクトの受け渡しに近い。
ポインタの問題点• 自由に制御でき過ぎる• 容易に無効な状態にできる• 指しているオブジェクトを管理していない
オブジェクトの所有権• ポインタはアドレスを保持しているだけで、その先にあるオブジェクトの寿命は管理していない。
オブジェクトの所有権• ダングリングポインタ• メモリリーク
ダングリングポインタ• オブジェクトが破棄されたのにポインタのアドレスがそのまま
• そのポインタを間接参照すると• 無効なオブジェクトへのアクセスとなる
void foo(){ Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 d->getSomeAnotherValue();! //僕「最後にあれをやっておこう」}
一度解放した領域を間接参照しようとした
void foo(){ Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 delete d; //僕「関数から抜けるので! //ちゃんと後片付けしておこう」}一度解放した領域をもう一度解放しようとした
void foo(){ Data *d = new Data(); d->getSomeValue(); delete d; //僕「先に綺麗にしておこう」 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 //...その他雑多な処理が沢山 delete d; //僕「関数から抜けるので! //ちゃんと後片付けしておこう」}一度解放した領域をもう一度解放しようとした
ダングリングポインタ
メモリーリーク• newで動的にメモリを確保し生成したオブジェクトのアドレスを紛失し、解放できなくなってしまうこと。
void foo(){ Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。}
このまま関数を抜けてしまうと、dのアドレスは誰も知らなくなる。dのアドレスの位置にあるオブジェクトはどうなる?
void foo(){ Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。}
C++にはGCが無いため、newで確保されたメモリ領域は、正しく解放しなければ、プログラム終了時までメモリ上に残ってしまう。
void foo(){ Data *d = new Data(); d->foo(); return; //おっと!delete d;を忘れている。}
C++にはGCが無いため、newで確保されたメモリ領域は、正しく解放しなければ、プログラム終了時までメモリ上に残ってしまう。
メモリーリーク
ポインタでメモリ管理• C言語では、free後にポインタを
NULLで初期化して、ポインタが無効であることを明示するのが一般的なメモリ管理の手法。
• プログラマが常に明示的にポインタの有効性を意識する
ポインタでメモリ管理
•一方C++はスマートポインタを使った
スマートポインタ• ポインタをラップしつつ、あたかもポインタのように扱えるようにしたクラス
• ポインタが指すオブジェクトの所有権を適切に管理できる
int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); }}
int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); }}
std::unique_ptrクラスのコンストラクタに、newで生成したオブジェクトのポインタを渡す。以降は変数dataが、newで生成したオブジェクトを管理する。
int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); }}変数dataはポインタのように扱える
int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); } //ここでdeleteが呼ばれる}スマートポインタの変数の寿命(この例だとスコープを抜ける時)で自動的に管理しているオブジェクトがdeleteされる。
int main(){ { std::unique_ptr<Data> data( new Data() ); data->doSomething(); } //ここでdeleteが呼ばれる}
このように、オブジェクトの寿命によってリソース管理を行う手法をRAII(Resource Acquisition Is Initialization)という。
スマートポインタ• RAIIによって、newで作成したオブジェクトを変数の寿命として管理できる。
スマートポインタ• RAIIによって、newで作成したオブジェクトを変数の寿命として管理できる。
• 例外安全性も高まる。• Exceptional C++
• 「例外安全入門」
int main(){ std::unique_ptr<int> pi(new int());
*pi = 1;
pi.reset(new int()); //新しいオブジェクトをセット //先に管理していた方は自動でdeleteされる
pi.reset(); //スマートポインタを初期化! //管理しているオブジェクトは! //自動でdeleteされる}
int main(){ std::unique_ptr<int> pi(new int());
*pi = 1;
pi.reset(new int()); //新しいオブジェクトをセット //先に管理していた方は自動でdeleteされる
pi.reset(); //スマートポインタを初期化! //管理しているオブジェクトは! //自動でdeleteされる}
newとdeleteと常に明示的に対応させて管理しなくてもよくなる。
スマートポインタ• 生のポインタより高機能• カスタムデリータ• オブジェクトが破棄されるときに行われる処理を指定できる。
スマートポインタ• 生のポインタより高機能• スマートポインタはその種類ごとに異なる特徴
スマートポインタ• std::unique_ptr
• コピー不可/ムーブ可
• newで作成したオブジェクトはただひとつのunique_ptrクラスのインスタンスだけで管理される
スマートポインタ• std::shared_ptr
• コピー可/ムーブ可
• newで作成したオブジェクトは複数のshared_ptrのインスタンスから共有される。
スマートポインタ• boost::scoped_ptr
• コピー不可/ムーブ不可
• newで作成したオブジェクトは一つのscoped_ptrのインスタンスで管理され、そのインスタンスがスコープから抜けるときに破棄される。
スマートポインタ• 生のポインタより高機能• スマートポインタはその種類ごとに異なる特徴
• これらを使い分けることで、コードの意味をより明示できる。
まとめ• ポインタの構文をおさらい
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる IntPointer pi = addressof(i);!! //piの値は0x7fff5fbfea54 indirect(pi) = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}
擬似コード
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}
実際のコード
int main(){ int i; //例えば変数iのオブジェクトは //0x7fff5fbfea54にいる int * pi = &i;!! //piの値は0x7fff5fbfea54 *pi = 3;! //piに代入されているアドレス経由で! //その位置にあるint型のオブジェクトを //操作する!! assert(i == 3):}
実際のコード
宣言
address-of
間接参照
まとめ• ポインタの構文をおさらい• スマートポインタを使おう。
まとめ• ポインタの構文をおさらい• スマートポインタを使おう。• メモリ管理の煩わしさを軽減する。
• コードの意味を明確にする。
その他ポインタと絡む話• ポインタと配列• ポインタと関数• メンバ変数のメモリ管理• ポインタと継承
最後にお知らせ• 「C++忘年会 2012 in 札幌」
• 12/15日(土)を候補に進めてます
• 興味が有る方は是非ご参加ください!!
最後にお知らせ• 「C++忘年会 2012 in 札幌」
• 12/15日(土)を候補に進めてます
• 興味が有る方は是非ご参加ください!!