2006-06-08
今回はメンバ関数について書くことにします。小難しい話は抜きにして、C++では、構造体のメンバとして、データだけではなく、関数を定義することができます。それが「メンバ関数」です。メンバ関数は、ごく大雑把にいうと、それが属している構造体のデータメンバ(フィールド)を操作するための関数です。コンストラクタやデストラクタも、実はメンバ関数の一種です。
メンバ関数のことを「メソッド」と呼ぶ人もいますが、「メソッド」と聞いてメンバ関数のことだと思っていたら、実は別の意味だったということも少なからずあります。言いたいことを正確に相手に伝えるには、C++の場合、「メンバ関数」と呼べば、間違いがありません。逆に、「メソッド」といわれた場合は、短絡的に「メンバ関数」と捉えずに、少し疑って文脈から判断するようにした方が無難です。
メンバ関数は、普通の関数(非メンバ関数)と同様に、返却値型と仮引数並びとともに宣言します。以下に簡単な宣言例を挙げます。
struct A
{
int value;
int func(int arg);
};
このように、データメンバと同様に、構造体のメンバの一種として、メンバ関数の宣言を行うことができます。そして、メンバ関数の呼び出しは、次のようにして行います。
A a;
int x = a.func(123);
メンバ関数は、あくまでも構造体のメンバですので、構造体A型のオブジェクトaに対して、.(ドット)演算子を使って呼び出します。また、A型へのポインタに対しては、
A* p = &a;
int y = p->func(456);
のように->(矢印)演算子を使って呼び出します。
メンバ関数が呼び出されると、内部的には次のようなことが起こります。
- 実引数とオブジェクトへのポインタ(ここでは&aまたはp)がコピーされる。
- 戻り先番地を退避し、funcを呼び出す。
- func関数では、実引数のコピーを仮引数として受け取るとともに、オブジェクトへのポインタをthisとして受け取る。
- returnによって、返却値がコピーされ、2.で退避した戻り先番地に戻る。
ここで普通の関数と異なるのは、オブジェクトへのポインタ(&aまたはp)がthisという名前のオブジェクトとしてメンバ関数に渡されることです。結果として、func関数の内部では、this->valueのようにすることで、構造体のデータメンバにアクセスすることができます。
なお、この「this->」は省略してもよいことになっています。ただし、データメンバと同名の識別子を関数内で宣言すると、そちらが優先されてしまうので注意が必要です。
では、次にメンバ関数の定義についてです。定義の方法は2種類あります。一つ目は、
struct A
{
int value;
int func(int arg)
{
return value += arg;
}
};
のように、宣言と同時に関数の定義も行ってしまう方法です。このように宣言と定義を同時に行った場合、原則としてインライン関数になりますので、ヘッダファイルの中に記述しても、関数の実体がいくつもできてしまうといった心配はありません。もう一つの方法は、
struct A
{
int value;
int func(int arg);
};
int A::func(int arg)
{
return value += arg;
}
のように、宣言と定義を分離する方法です。この場合、普通は、宣言をヘッダファイルに記述し、関数の実体は(ヘッダファイルではない)ソースファイルに記述することになります。関数の実体を定義するときには、そのメンバ関数がA型に属していることを表すために、A::という接頭辞を付ける必要があります。
ところで、メンバ関数にはオブジェクトへのポインタがthisとして渡されると書きました。では、メンバ関数の呼び出しに使ったオブジェクトの型が、constやvolatileで修飾されている場合はどうでしょう。何らかの方法で、CV修飾の情報を記述できる必要があります。そこで、メンバ関数では、thisのCV修飾の状態を表すために、次のような書き方ができます。
struct A
{
int value;
int func(int arg); // (1)
int func(int arg) const; // (2)
int func(int arg) volatile; // (3)
int func(int arg) const volatile; // (4)
};
上記の(1)はCV修飾なし、(2)はconst修飾付き、(3)はvolatile修飾付き、(4)はconst volatile修飾付きを意味します。このようにメンバ関数は、オブジェクトのCV修飾子の有無によって多重定義することができます。
なお、何度も書くように、メンバ関数は普通の関数とは異なり、オブジェクトへのポインタがthisとして渡されます。すなわち、普通の関数とはコーリング・コンベンションが異なるわけです。よくある間違いに、メンバ関数へのポインタを、普通の関数へのポインタに代入しようとして、コンパイルできずに悩むということがあります。メンバ関数へのポインタを取得することは可能ですが、それについては、また別の機会に書きたいと思います。
Comment