こんにちは、ナナです。
「thisポインタ」とはメンバ関数の中でこっそりと存在する、隠されたポインタです。
オブジェクトに作用するメンバ関数は、この「thisポインタ」によって動いています。それほど、大事な存在なのです。
「thisポインタ」の役割と、使い方を解説していきましょう。
「thisポインタ」をプログラムから使ってみる
師匠!何やら、隠された秘宝があると聞きつけました。その名も「thisポインタ」。いったいどんな秘宝なのですか?!
秘宝ってほどでもないですが、オブジェクト指向言語を動かすうえでは欠かせないもの、それが「thisポインタ」です。
実はすでにみんなはお世話になっているんですよ。隠された「thisポインタ」を、まずは明るみに出してみましょう!
「thisポインタ」を使ったプログラム
今まで明確に使用していなかった「thisポインタ」を実際にプログラムで使ってみます。
#include <stdio.h>
class POS
{
private:
int x;
int y;
public:
POS(int tmpx, int tmpy);
void printPos();
};
// コンストラクタ
POS::POS(int tmpx, int tmpy)
{
this->x = tmpx;
this->y = tmpy;
}
// 座標表示
void POS::printPos()
{
printf("x:%d y:%d", this->x, this->y);
}
int main()
{
POS posA(100, 200);
posA.printPos();
return 0;
}
白抜き部分が「thisポインタ」を使っている箇所です。
「thisポインタ」を使った表現と、使わない表現を比べてみましょう。違いが明確にわかりますね。
// コンストラクタ
POS::POS(int tmpx, int tmpy)
{
this->x = tmpx;
this->y = tmpy;
}
// 座標表示
void POS::printPos()
{
printf("x:%d y:%d", this->x, this->y);
}
// コンストラクタ
POS::POS(int tmpx, int tmpy)
{
x = tmpx;
y = tmpy;
}
// 座標表示
void POS::printPos()
{
printf("x:%d y:%d", x, y);
}
このように、「thisポインタ」はクラスオブジェクトのメンバにアクセスする際に使われるポインタなのです。
メンバ関数の中で「thisポインタ」の記述は省略することが可能です。そのため、今まで隠れていたのです。
デバッガにも表示される「thisポインタ」
Visual Studio のデバッガにもちゃんと「thisポインタ」は表示されます。次のようにメンバ関数の中にステップ実行で入ってみましょう。
ローカルウィンドウに「this」と表示されているのがわかりますね。これが「thisポインタ」です。
クラスメンバである「x」「y」がぶら下がって値も表示されています。
「thisポインタ」が指し示すモノとは何なのか?
師匠!クラスのメンバにアクセスするためのポインタが「thisポインタ」ってことですねっ。うーん、わかったような、わからないような・・・。
ポインタってことは、何かを指し示しているんですよね。「thisポインタ」はどこを指し示しているんですか?
いいですね。「ポインタ」を使う時には「ポインタがいったいどこを指し示しているか?」は、すごく大事な視点です。
「thisポインタ」は何を指し示しているのかをはっきりさせましょう。
「ポインタ」とは、とあるメモリを指し示すためのものです。
「thisポインタ」が指し示すものとは?
それでは実際のプログラムで、ポインタの番地を画面に表示してみましょう。
#include <stdio.h>
class POS
{
private:
int x;
int y;
public:
POS(int tmpx, int tmpy);
void printPos();
};
// コンストラクタ
POS::POS(int tmpx, int tmpy)
{
x = tmpx;
y = tmpy;
}
void POS::printPos()
{
printf("this:0x%p x:%d y:%d\n\n", this, this->x, this->y);
}
int main()
{
// posAオブジェクト
POS posA(100, 200);
printf("posA:0x%p\n", &posA);
posA.printPos();
// posBオブジェクト
POS posB(300, 400);
printf("posB:0x%p\n", &posB);
posB.printPos();
return 0;
}
posA:0x012FF774
this:0x012FF774 x:100 y:200
posB:0x012FF764
this:0x012FF764 x:300 y:400
結果をよく見てみましょう。printPosメンバ関数の中で表示している「thisポインタ」の番地が1回目と2回目で変化しています。
しかも、直前で表示している「posA」と「posB」のクラスオブジェクトの番地と同じであることがわかります。
つまり、「thisポインタ」が指し示すものとは
メンバ関数を呼び出した「クラスオブジェクト」へのポインタ
です。
そのため、posAからの1回目の呼び出しと、posBからの2回目の呼び出しで「thisポインタ」の番地内容が変化しているのです。
これこそが、「オブジェクト指向」が動作する原理なのです。
メンバ関数を呼び出したオブジェクトによって振る舞いが変化するのは、この「thisポインタ」のおかげなのです。
「thisポインタ」を利用したメンバ関数の振る舞いが変わる仕組み
プログラムの中には魔法はありません。必ず実現するための手段が用意されています。
呼び出したオブジェクトによってメンバ関数の結果が変化する、その仕組みを明らかにしていきます。
その仕組みとは、オブジェクトによってメンバ関数の振る舞いを変化させるために、オブジェクトへのポインタである「thisポインタ」をメンバ関数にこっそり渡すのです。
このようにすることで、呼び出したオブジェクトが「posA」でも「posB」でもメンバ関数はオブジェクトの違いを意識することができるのです。
この仕組みってC言語の「ハンドル」と同じですよね。「ハンドル制御」関数の引数に「ハンドル」が必要であるのと同じで、メンバ関数の引数には「thisポインタ」が必要なのです。
結局、プログラムが動作する原理は一緒ってことなんです。
Q&A:thisポインタに関するよくある質問
Q:省略できるのであれば「this->」って書く必要ないよね?
師匠!メンバ関数の中で「this」って省略できるんですよね。じゃあ、書く意味ないじゃないですかっ!
そこは好みもあるけど、書くと便利なこともあるんだよ。
メンバ関数の中で「thisポインタ」を明確に指定したアクセスは次のメリットがあります。
ローカル変数やグローバル変数との明確な違いを表現できる
メンバ関数の規模も大きくなると、変数が「ローカル変数」なのか「グローバル変数」なのか、はたまた「メンバ変数」なのか判断が付きづらくなります。
その中で「this->x」といった表現方法は確実に「メンバ変数」であることを認識できます。
ローカル変数とメンバ変数の名前がバッティングしても大丈夫
メンバ関数の引数は、メンバ変数名と被りやすい名前が多くなります。「thisポインタ」を使わない場合は、左のプログラムのようにローカル変数とメンバ変数の名前を工夫する必要があります。
POS::POS(int tmpx, int tmpy)
{
x = tmpx;
y = tmpy;
}
POS::POS(int x, int y)
{
this->x = x;
this->y = y;
}
しかし、thisポインタを使った右側のプログラムの場合は、名前が被っても問題ありません。より自然な変数名でプログラミングすることができます。
Q:「thisポインタ」が必要なシーンとは?
師匠!書くと便利な場面があるのはわかりましたが、なきゃないでなんとかできそうですよね。
「thisポインタ」が絶対に必要になるシーンなんてあるんですか?
そうだね~、メンバ関数の中で呼び出したオブジェクト自身が必要となるシーンがあったりします。そういった場面では「thisポインタ」は必要になるんです。
メンバ関数から見えている呼び出し元のオブジェクトを「自オブジェクト」と呼んだりしますが、メンバ関数の中からこの「自オブジェクト」が必要となることがあります。
次のような、他の関数に自オブジェクトを渡したい、呼び出し元に自オブジェクトの参照を渡したい、といったシーンです。
POS & POS::func()
{
// 別の関数へ自オブジェクトを渡す
subfunc(*this);
// 呼び出し元にオブジェクトの参照を返す
return *this;
}
「thisポインタ」は時には必要なシーンが出てきますので、知識としては必ず知っておきましょう!