はじめに

このページではプログラミングに関するメモを掲載しています。

現在掲載している他にも注意しておきたい点がありましたらどんどん編集していってください。

C++におけるポインタのポインタについて

ポインタのポインタって何なの?なんでそんなもの使うの?バカなの? という声を複数聞いたので、ちょろっと解説してみようと思います。

の3本立てにするので、分かってる所は読み飛ばしても良いです。 最初の違いについては、この説明文読む上での前提を示すので、分かってても一応読んだ方がいいかもしれません。

ポインタとアドレスの違い

この説明ではポインタとアドレスという言葉を区別して用います。 よくポインタ=アドレスと解釈してる人もいるのですが、それはある意味正しく、ある意味間違いです。C/C++ではそれで正しいのですが、言語によっては必ずしもそうではないためです(Javaとか)。

ポインタの本来の意味は「変数やインスタンスを一意に指し示す(個体識別する)もの」であり、一意に指し示すために具体的に何が使われるかは言語や処理系により様々です。C/C++ではメモリアドレスが使われますが、ユニークなIDによってインスタンスを識別する言語もあります。

私自身がC++erであるので、この説明もC++を前提として述べます。 その上で、次のように単語を使い分けます。

一般的な変数における値と変数の関係だと思ってください。 アドレスが値で、それを代入するための変数がポインタです。

ポインタのポインタとは

通常の変数からアドレスを取得し、ポインタに代入するには次のようにします。

    int a = 10;   // 普通のint型の変数を用意
    int *p = &a;  // aのアドレスをポインタpに代入

ポインタも変数の一種ですので、当然ポインタ自体にもアドレスがあることになります。 今pの値にはaのアドレスが入っていますが、それではなくてポインタp自体のアドレスを取り出して代入するには次のようにします。

    int **pp = &p; // p自体のアドレスをポインタのポインタppに代入

この時のppが「ポインタのポインタ」です。 ポインタに入っている値ではなく、ポインタ自体のアドレスを指していることに注意しましょう。

何故それを使うのか

私が把握している限り、ポインタのポインタを用いる理由は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で返すことが定例になっているので、単純に返値がふさがってるからダブルポインタになってる、だけなのかもしれないですが、恐らく今回述べたような目的も多分にあるのではないか、と推測します。

一見不可解な設計や自分が苦手な記法にも、必ず意図や経緯があるはずです。何故そうなっているのかを考察するのは良い経験になると思いますし、その上で自分も取り入れるかどうかを考えるなり、実践するなりしていくとスキルアップに繋がるんじゃないかと思います。


クラスのメンバに配列を持った場合、どう初期化するべきか

ここがイヤだよC++

C言語の解説サイトなどで、配列の解説の時に、

    int array[3] = {1, 2, 3};

などと記述してあるのを良く見ます。 じゃあ、これがメンバ変数になった時は、

// これは駄目コードです
class Hoge {
  private:
    int array[3];
  public:
    Hoge()
    {
        array = {1, 2, 3};
    };
}

これが許されるの?と思いたくなりますが、残念ながら駄目です。 配列を直接突っ込めるのは、その配列を実際に宣言する時だけなのです。 上記のコードだと、arrayはメンバ変数ですが、{1, 2, 3}は「その場に一時的に作られた配列」でしかないため、そもそもの寿命が釣り合いません。

じゃあ初期化リストならいけるんじゃん?と思うのは極々自然なことだと思います。 ですが、残念なことに現状のC++では策定されていない記法です。次期標準仕様候補のC++0xでは出来るみたいなので、それに期待するしかないですね。 (参考サイト http://d.hatena.ne.jp/faith_and_brave/20080718/1216371614 )

定数値の場合

じゃあどないせぇっちゅうねん!て話ですが、メンバ「定数」としたいのならば以下のような記法で行けます。

// 宣言部(h)
class Hoge {
  private:
    static const int array[3];
};

// 定義部(cpp)
// 宣言ヘッダをインクルードした直後に、どこの関数スコープにも属さない場所で
const int Hoge::array[3] = {1, 2, 3};

staticメンバは、その実体定義と初期化をクラス宣言の外部で行う必要があります。これならば配列をダイレクトに代入することが出来ます。

constを外せば配列内の値を変更することも出来ますが、あくまでstaticメンバなので全インスタンス共通の値になってしまい「各インスタンス固有の値」にはなりません。

メンバ「変数」として配列を扱いたい場合

{}で囲った配列の記法での代入にとことんこだわる場合は、以下のような記法が考えられます。

class Hoge {
  private:
    int array[3];
  public:
    Hoge()
    {
        int tmpArray[3] = {1, 2, 3};

        for(int i = 0; i < 3; i++) {
            array[i] = tmpArray[i];
        };
    };
};

配列のコピーは単純なforループで実現できるので、いったん{}記法で作成した配列からメンバ配列へコピーする、というのが新たな知識を得ずともなんとかなる方法かと思います。

こういう所がC++が泥臭いと言われる所以なんでしょうね…。





トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS