- 追加された行はこの色です。
- 削除された行はこの色です。
*はじめに [#x0b91475]
#contents
このページではプログラミングに関するメモを掲載しています。
現在掲載している他にも注意しておきたい点がありましたらどんどん編集していってください。
*C++におけるポインタのポインタについて [#f6444b53]
ポインタのポインタって何なの?なんでそんなもの使うの?バカなの?
という声を複数聞いたので、ちょろっと解説してみようと思います。
-ポインタとアドレスの違い
-ポインタのポインタとは
-何故それを使うのか
の3本立てにするので、分かってる所は読み飛ばしても良いです。
最初の違いについては、この説明文読む上での前提を示すので、分かってても一応読んだ方がいいかもしれません。
**ポインタとアドレスの違い [#b2ba82cd]
この説明ではポインタとアドレスという言葉を区別して用います。
よくポインタ=アドレスと解釈してる人もいるのですが、それはある意味正しく、ある意味間違いです。C/C++ではそれで正しいのですが、言語によっては必ずしもそうではないためです(Javaとか)。
ポインタの本来の意味は「変数やインスタンスを一意に指し示す(個体識別する)もの」であり、一意に指し示すために具体的に何が使われるかは言語や処理系により様々です。C/C++ではメモリアドレスが使われますが、ユニークなIDによってインスタンスを識別する言語もあります。
私自身がC++erであるので、この説明もC++を前提として述べます。
その上で、次のように単語を使い分けます。
-アドレス:変数やインスタンスを一意に指し示す値
-ポインタ:アドレスを格納するための変数
一般的な変数における値と変数の関係だと思ってください。
アドレスが値で、それを代入するための変数がポインタです。
**ポインタのポインタとは [#ba0646c5]
通常の変数からアドレスを取得し、ポインタに代入するには次のようにします。
int a = 10; // 普通のint型の変数を用意
int *p = &a; // aのアドレスをポインタpに代入
ポインタも変数の一種ですので、当然ポインタ自体にもアドレスがあることになります。
今pの値にはaのアドレスが入っていますが、それではなくてポインタp自体のアドレスを取り出して代入するには次のようにします。
int **pp = &p; // p自体のアドレスをポインタのポインタppに代入
この時のppが「ポインタのポインタ」です。
ポインタに入っている値ではなく、ポインタ自体のアドレスを指していることに注意しましょう。
**何故それを使うのか [#j3039033]
私が把握している限り、ポインタのポインタを用いる理由は2つ考えられます。
1つは「2次元配列」です。
m行n列のような配列だったら1次元配列で事足りますが、行毎に列の長さが異なるような配列の場合は、ポインタのポインタ(ポインタの配列、と呼んだ方が分かりやすいかも)を用いた方がスマートで効率的です。
これについては説明しているサイトが他にもありますので、そちらに説明は譲ります。
(参考サイト→ http://www2.netf.org/pointer4.html )
もう1つは「アドレスの代入を強制させるため」です。これがこの説明のメインです。
いきなり斬り込むのではなく、前段として例を挙げます。
ある計算結果をint型の値で返してくる関数があるとします。
とても大事な計算結果なので、関数を作った人としては「必ずちゃんと変数に代入して、捕まえて欲しいなぁ」と思っているものとします。
ですが、関数を呼び出す時には返値を必ず代入しなければならないという決まりはありません。
関数の作成者の祈りも空しく、次のようなコードを書かれてしまうことはあり得ます。
hoge(); // 本当は大事な結果を返すのに……
これを防ぐために、関数hoge()を次のように作りかえました。
void hoge(int *p)
{
*p = 1919; // とても大事な計算結果をpが指す変数に代入
}
このように作りかえた場合、関数hoge()は次のようにしないと呼べません。
int a = 0;
hoge(&a);
int型のポインタを引数に取ることで、必ず変数のアドレスを渡さなければいけなくなりました。
返値の場合は値を受け取ってもらえる保証はありませんでしたが、こうすることで確実に値を変数に代入できるわけです。
さて、本題です。
あるクラスHogeのインスタンスを手続きに沿って生成し、そのアドレスを返す処理を関数化したいケースを考えます。その関数以外ではnew Hoge()をするのは禁止されているとします。
シンプルに考えるのならば、
Hoge * makeHoge()
{
return new Hoge();
}
こんな関数を作って、受け止めてもらえたらいいと思うでしょう。
ですが、
makeHoge(); // リーク確定切腹モノコード
こういうコードを書かれたら身もフタもありません。
(Hogeクラス側でインスタンスを管理するような仕組みを導入してれば話は別ですけども。)
これを防ぐために、前段で述べた仕組みを導入します。
void makeHoge(Hoge **ppH)
{
*ppH = new Hoge(); // ppHが指しているポインタに、newして得られたアドレスを代入
}
Hogeクラスのポインタのポインタを、引数として取るようにします。
なので、makeHoge()にはHogeクラスのポインタのアドレスを渡さないと呼べなくなります。
Hoge *pH = NULL;
makeHoge(&pH;);
こうすれば、newして得られたアドレスを必ずポインタに代入することになりますので、リークの危険性は下がるだろう、というのがこの設計の狙いだと思われます。
とまぁ長々と説明しましたが、ポインタへの代入に強制力を持たせても、他にリークする要因はいくらでも考えられるので、あまり効果的ではないのではないかと個人的には思います。私は実際の設計方針ではまず使いませんし、極力回避します。
ダブルポインタがこういう目的で頻出するのは、DirectXを扱う時が多いと思います。MicrosoftのAPIは関数の動作の正否をHRESULTで返すことが定例になっているので、単純に返値がふさがってるからダブルポインタになってる、だけなのかもしれないですが、恐らく今回述べたような目的も多分にあるのではないか、と推測します。
一見不可解な設計や自分が苦手な記法にも、必ず意図や経緯があるはずです。何故そうなっているのかを考察するのは良い経験になると思いますし、その上で自分も取り入れるかどうかを考えるなり、実践するなりしていくとスキルアップに繋がるんじゃないかと思います。
~
~
~