C++ 関数ポインタ関係 書いて覚えるための初心者自己中記事
関数のアドレスは取得できる
そして関数のアドレスを入れられる変数もある。
らしい。
関数のアドレスが取得できるという話は以前の勉強でやった覚えがある。
コレの中で、
~~~~~~~~~~~~~~~~~~~~~~~
this とは
Franction& operator *=(const Franction& rop);
Franction& Franction::operator*=(const Franction& rop) { m_numer *= rop.m_numer; m_denom *= rop.m_denom; return *this;// this はこのメンバ関数を呼び出したオブジェクトのアドレスを持つポインタ }
~~~~~~~~~~~~~~~~~~~~~~~
こんな感じだった。
ん?
これはあれか、メンバ関数を呼び出したオブジェクトのアドレスか。
・・・。
勘違い。
this は今回でないのかな。
とにかく、関数のアドレスを入れられる
関数ポインタ
について勉強。
おっと、テキストの序盤で書いてある。
アドレスを露出させるには
変数なら & をつける
配列や関数は 名前がそのままアドレス。
配列は
配列名 [ インデックス ] ではなくて
アドレス [ インデックス ]
今回は検証しながら正解に行く流れらしい。
こんな関数があるとして
int fp(int a, int b);
こうしてしまうと戻り型がポインタになってしまう。
int *fp(int a, int b);
戻り型と混ざらないように
int (*fp)(int a, int b);
こうしてみる。
とりあえずこれで良しとして、配列にするらしい。
int (*fp[4])(int a, int b);
・・・。関数名が配列ってちょっと突然ですね。
多分関数ポインタの説明に必要なんだろうけど。
関数名も配列名のアドレスだから同じことなのかな?
fp = Func;
ちょっ・・・。
えっと?
Func が関数ということらしい。
fp は関数ポインタということらしい。
さっきまで関数プロトタイプでfp にどうやって * をつけるかしていたけど
その fp に 関数Func を代入しちゃった。
cout << fp(1, 2) << endl;
そして呼び出した。
もう置いてけぼりですよ。
と思ったら
//配列のサイズを取得 #define ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) //四則演算を行う関数たち int Add(int a, int b) { return a + b; } int Sub(int a, int b) { return a - b; } int Mul(int a, int b) { return a * b; } int Div(int a, int b) { return a / b; } //四則演算を行う関数たちをまとめた配列関数ポインタ int(*const FP_OPERATOR[])(int a, int b) = { Add, Sub, Mul, Div, }; //演算名 const char* const OPERATION_NAME[] = { "加算", "減算", "乗算", "除算" }; //入力した値に四則演算を順に適用した結果を出力するクラス class Calculator { public: void Run(); //処理の実行 private: bool Input(); //二つの値を入力 void Calculate(); //二つの値を演算 private: int m_a, m_b;//二つの値 }; void Calculator::Run() { while (Input()) { Calculate(); } } bool Calculator::Input() { m_b = 0; cout << "2つの値を入力してください。" << endl; cin >> m_a >> m_b; return m_b != 0; } void Calculator::Calculate() { static const size_t SIZE = ARRAY_SIZE(FP_OPERATOR); assert(SIZE == ARRAY_SIZE(OPERATION_NAME));//2つの配列の要素数をチェック cout << "演算結果" << endl; for (size_t i = 0; i < SIZE; ++i) { cout << OPERATION_NAME[i] << ' ' << FP_OPERATOR[i](m_a, m_b) << endl; } } int main() { Calculator cal; cal.Run(); system("pause"); }
さっきまでのもやもやが嘘のようにすっきりした。
長ったらしいけど大事なのはここと
//四則演算を行う関数たち int Add(int a, int b) { return a + b; } int Sub(int a, int b) { return a - b; } int Mul(int a, int b) { return a * b; } int Div(int a, int b) { return a / b; } //四則演算を行う関数たちをまとめた配列関数ポインタ int(*const FP_OPERATOR[])(int a, int b) = { Add, Sub, Mul, Div, };
ここだけ
FP_OPERATOR[i](m_a, m_b)
引数に無理がない状態の関数を一つの配列関数ポインタに格納しちゃっている。
呼び出すときは配列関数名に要素番号で引数
関数ポインタに格納できる関数の条件は
戻り値の型
引数の数
引数の順番
が一致していること。
関数ポインタの型名
型名?
変数の型名は例えば
int * n;
だと
int *
になるらしい。
同じように変数名(関数名) と ; を外したものが型名になるらしい。
今回のコレの場合は
int(*const FP_OPERATOR[])(int a, int b);
int(*)(int a, int b)
こうなる。
これが型名。
これが何なのかというと、
typedef で仮の名前を設定するときに型名が必要だから。
typedef って何だっけ?と思い以前の勉強からコピペ
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
処理を書くときに型を何にするか確定できていない場合に
typedef で設定した仮の名前を使い、最後にまとめて設定する事が出来る。
typedef int ValueType; ValueType m_a, m_b; //今はint型 ValueType m_result; //今はint型
typedef double ValueType; ValueType m_a, m_b; //今はdouble型 ValueType m_result; //今はdouble型
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef 型名 仮の名前
という書き方ね。
これを関数ポインタで行う場合にさっきの型名が必要で。
typedef int(*Test)(int a, int b); //typedefった
Test FP_OPERATOR[] = { Add, Sub, Mul, Div, };
こうなる。
仮の名前は関数名があるべき場所にちゃんと書くのね。
クラスの外にあった配列関数ポインタをとそれに入る関数たちをクラスの中に招きました。の場合。
変更部分だけ。
private: //四則演算を行う関数たち static int Add(int a, int b) { return a + b; }//staticで静的にした static int Sub(int a, int b) { return a - b; }//staticで静的にした static int Mul(int a, int b) { return a * b; }//staticで静的にした static int Div(int a, int b) { return a / b; }//staticで静的にした typedef int(*Test)(int a, int b); //typedefった //四則演算を行う関数たちをまとめた配列関数ポインタ static const Test FP_OPERATOR[];//staticで静的にした //演算名 static const char* const OPERATION_NAME[];//staticで静的にした private: int m_a, m_b;//二つの値 }; //四則演算を行う関数たちをまとめた配列関数ポインタ const Calculator::Test Calculator::FP_OPERATOR[] = {//ひたすらクラス名つける Calculator::Add, Calculator::Sub, Calculator::Mul, Calculator::Div, }; //演算名 const char* const Calculator::OPERATION_NAME[] = {//ここは普通に実装 "加算", "減算", "乗算", "除算" };
配列関数ポインタの実装部分でひたすらクラス名をつけるのがやばい。
静的メンバにしたということはクラスのオブジェクトにあるんじゃなくて
クラスに1つだけ存在するってことでしたね。
このクラスのオブジェクトを複数作っても静的メンバはその都度作られるわけではない。と。
で、次は静的ではないメンバ関数でポインタに入れたい場合の話。
なぜ静的ではないメンバ関数にしなければならないのかというと、
現状四則演算をする静的メンバ関数は配列関数ポインタ経由で引数をもらっている。しかしこの引数はそもそもこのクラスのメンバ変数なのでわざわざ引数でもらうのではなくて、そのまま使いたい。
そうなってくると静的メンバはオブジェクトごとに実体があるわけではないのでオブジェクトごとに違う数値が入っているメンバ変数を使えないというわけだ。
なので四則演算をするメンバ関数を静的ではないメンバ関数にして、オブジェクトごとに実体を持たせたいとう話。
まず静的でないものにするためにstatic を外します。
引数もいりません。
かわりにメンバ変数を使います。
private: //四則演算を行う関数たち int Add() { return m_a + m_b; }//static外した引数なくしてメンバ変数入れた int Sub() { return m_a - m_b; }//同上 int Mul() { return m_a * m_b; }//同上 int Div() { return m_a / m_b; }//同上 typedef int(*Test)(); //typedef 引数外した //四則演算を行う関数たちをまとめた配列関数ポインタ static const Test FP_OPERATOR[];//staticで静的のまま //演算名 static const char* const OPERATION_NAME[];//staticで静的にした private: int m_a, m_b;//二つの値 }; //四則演算を行う関数たちをまとめた配列関数ポインタ const Calculator::Test Calculator::FP_OPERATOR[] = {//ひたすらクラス名つける Calculator::Add, Calculator::Sub, Calculator::Mul, Calculator::Div, };
配列関数ポインタは静的のまま。
この状態は、
クラスのオブジェクト
静的な配列関数ポインタ
の流れ。
やばいわけわからない。
えっと
ただ、この場合オブジェクトから静的な配列関数ポインタにはthis が渡されない。(this == 呼び出したオブジェクトのアドレス)
で、静的な配列関数ポインタは、だれが自分を呼び出したのかわからないから呼び出したオブジェクトと同じオブジェクトのメンバ関数を特定できない。
ということなのだろうか・・・・。
そのためにまずは呼び出し側でthis をつかうのかな。
cout << OPERATION_NAME[i] << ' ' << (this->*FP_OPERATOR[i])() << endl;//呼び出したオブジェクトのアドレスを指定する
メンバ関数の中での呼び出しだけど。
ん、テキストで気になる発言
静的メンバ変数として普通の関数ポインタを作った場合を~~
そうか、関数ポインタを関数だと思い込んでしまっていた。
早とちりの思い込みだ。
関数のアドレスを代入出来るポインタというだけで、それは関数ではなくて変数なのか。
話を戻して、
静的なメンバ変数は実装時にクラス名を指定する必要がある。
Integer::Integer(int num) { m_value = num; } int Integer::m_value; //staticメンバ変数の実体
さっきの静的な配列関数ポインタもクラス名と指定しまくってた。
//四則演算を行う関数たちをまとめた配列関数ポインタ const Calculator::Test Calculator::FP_OPERATOR[] = { Calculator::Add, Calculator::Sub, Calculator::Mul, Calculator::Div, };
~~~
二時間後
~~~
いや、ちょっと理解するためには情報をもっと減らさないとだめだわ。
もっと簡素に簡素に。
class Calculator { public: Calculator() :m_a(0), m_b(0) {}; void Show() { cout << FP_OPERATOR[0](3, 7) << endl;; } private: static int Add(int a, int b) { return a + b; } typedef int(*Test)(int a, int b); static const Test FP_OPERATOR[]; private: int m_a, m_b; }; //関数ポインタ実装 const Calculator::Test Calculator::FP_OPERATOR[] = { Calculator::Add, }; int main() { Calculator cal; cal.Show(); system("pause"); }
まず、静的メンバ関数の場合はこうだった。
これはまだわかる。
static だからクラス名を指定している。
そこからメンバ関数の引数をなくしてメンバ変数を使うようにする。
あわせてメンバ関数からstatic をとって静的でなくする。
Input とかも今は邪魔だからコンストラクタでメンバ変数の初期値をいれちゃう。
class Calculator { public: Calculator() :m_a(3), m_b(7) {}; void Show() { cout << FP_OPERATOR[0]() << endl; } private: int Add() { return m_a + m_b; } typedef int(*Test)(); static const Test FP_OPERATOR[]; private: int m_a, m_b; }; //関数ポインタ実装 const Calculator::Test Calculator::FP_OPERATOR[] = { Calculator::Add, }; int main() { Calculator cal; cal.Show(); system("pause"); }
この状態だとコンパイルエラーになる。
ここまではテキストも一緒。
で、基本事項
ポインタ -> メンバ名 でアクセス出来る
class Calculator { public: Calculator() :m_a(3), m_b(7) {}; void Show() { cout << (this->*FP_OPERATOR[0])() << endl; } private: int Add() { return m_a + m_b; } typedef int(Calculator::*Test)(); static const Test FP_OPERATOR[]; private: int m_a, m_b; }; //関数ポインタ実装 const Calculator::Test Calculator::FP_OPERATOR[] = { &Calculator::Add, }; int main() { Calculator cal; cal.Show(); system("pause"); }
あぁ~~~。
ダメだ、ずっと考えてたから形だけは覚えたけど意味が分からない。
関数ポインタ実装で代入しているメンバ関数に & が付いているのは
静的で無くなったメンバ関数からアドレスをもらうため。
(静的だったらいいの?)(いや、静的関数って、まぁ普通の関数と同じような感じだからいいのかも)
Show関数内で関数ポインタを呼び出しているときの
( this -> *関数ポインタ名) ( 引数 )
はオブジェクトのポインタを使って関数ポインタを呼び出すため。
脳内を整理すると、Show関数を呼び出したオブジェクトからthisポインタが来ているわけで、そのthisポインタをつかって関数ポインタを呼んでいる。
それってつまり、Show関数が関数ポインタを呼んでいるときに、大本で呼んでいるオブジェクトのアドレス(thisポインタ) が関数ポインタにも渡っていうということ?なのか?
thisポインタを使って呼んでいる、って言うくらいだし。
あとは、なんだ、typedef してる型の中にクラス名を指定しているあたりが理解できない。
テキストで、
メンバ関数を呼ぶときにはそれを呼ぶ側のオブジェクトのポインタ(thisポインタ)が渡されている。そうしないとメンバ関数は使えない。
↓
だから関数ポインタにクラス名を指定しよう。
↓
typedef 部分の型で、仮の型名にクラス名を指定しよう。
この流れがわからない。
、、仮の型名にクラス名を指定しているんじゃないのか。
型とは
~~~~
int(*const FP_OPERATOR)();
int(*)()
こうなる。
これが型名。
~~~~
ということだったからクラス名を指定してやると
int(Calculator::*const FP_OPERATOR)();
int(Calculator::*)()
となって、それをtypedef で使うと
#typrdef int(Calculator::*Test)();
となるのか。
ただ、そもそもなんでクラス名を指定するんだろう。
テキストでは、
そのクラスのオブジェクトを指定する必要がある。
って書いている。
それはわかっているつもりだけど。
そこから、
なのでクラス名を指定してあげればいい。
になるの?
オブジェクトの指定はどこ行ったの?
もしかして、
関数ポインタに集まっている情報を考えると
・Show関数からは呼び出したオブジェクトのアドレス thisポインタ
・関数ポインタに代入されたメンバ関数のアドレス
・関数ポインタ内でクラス名の指定
が集まっている。
・クラス名についてだけど、クラス名がわかってもそれは何の役に立つの?
・thisポインタはわかる。
・代入されたメンバ関数のアドレスってのは、オブジェクトごとに違うはずだからどのオブジェクトのメンバ関数か知るためにはthisポインタが渡ってきているからそれを見ているんだろうか。
thisポインタとメンバ関数のアドレスがあれば事足りるのでは?
クラス名を指定しなければならない理由がまだわからない。
あー。
オブジェクトのアドレスとメンバ関数のアドレスがわかっても
大枠がわからないってことかな。
まず、こんなクラスがあるよ~~
っていう前提がないと
オブジェクトのアドレスとメンバ関数のアドレスだけだと
ただのアドレス値の集会になるのか。
だから必要なのが
クラス名
オブジェクトのアドレス
メンバ関数のアドレス
なのか。
ものすごく間違っているかもしれないけど。
とりあえず理屈はつけられた。
理屈がないと覚えられないから仕方ない。
まだまだつづく
この関数ポインタは仮想関数を代入した場合に適切なクラスに誘導してくれるのか。という話。
class Base { public: virtual ~Base() {}; virtual void Hoge() { cout << "Base::Hoge" << endl; } }; class Derived :public Base { virtual void Hoge() { cout << "Derived::Hoge" << endl; } }; void CallHoge(Base& b) { void(Base::*mfp)() = &Base::Hoge;//基底クラスのメンバ関数で初期化 (b.*mfp)(); } int main() { Derived d; CallHoge(d); system("pause"); }
//output
Derived::Hoge
これは大丈夫。ちゃんとCallHogeは基底クラスの型で参照した派生クラスのオブジェクトを誘導してくれた。
さっきまで納得するために考えていた内容と照らし合わせると若干無理があって、
基底クラス名を指定
代入したのは基底クラスのメンバ関数アドレス
渡ってきたオブジェクトアドレス(thisポインタ)
これだとちょっと無理がある。
ただし、ここで仮想関数を最適に誘導するための仕組みはコンパイラに依存しているそうで、テキストでも詳しくは書いていない。
また、こういった特殊な動きをさせる為なのか、メンバ関数ポインタは普通のポインタとサイズが違っている場合があるとかなんとか。
そこらへんでうまいことやってくれているんだと思われ。
つぎに、
メンバ変数ポインタ
・・・?
普通のポインタと違うの?
テキストを読もう。
まず、
//構造体(久しぶり) struct point { int x, y; }; //引数で受け取ったサイズの数まで1増やす void IncX(point* pt, int size) { for (int i = 0; i < size; ++i) { ++pt[i].x; } } void IncY(point* pt, int size) { for (int i = 0; i < size; ++i) { ++pt[i].y; } } int main() { point pt; IncX(&pt, 3); IncY(&pt, 3); system("pause"); }
クラスですらないこんなのを用意。
あ、構造体も中のはメンバだっけ・・。
そんでメンバ変数ポインタとはこういったものらしい。
int point::*mp = &point::y;
書き方メンバ関数ポインタにならえばいい感じかな。
なになに・・?
ここでいう &point::y
はオブジェクトのアドレスとかではなくて
構造体 point のなかで メンバ変数 y はどの位置にあるのか。
ということらしい。
んで、それを mp というメンバ変数ポインタに代入している。
つまり構造体point のオブジェクトがあればそこに mp を与えれば
メンバ変数 y を指定していることになる。
一応誤解のないように書くと、
int point : : *mp = &pt.y ;
とかやると、特定のオブジェクトのメンバ変数の位置になるのでダメ。
たぶん、メンバ変数ポインタに代入したのは相対的な位置関係なんだと思う。
だから、同じpoint 型のオブジェクトを作っても使える。
んで、それを使ってまとめると・・
っていつも思ってるんだけど、まとめたほうが複雑で長くなっている気がする・・。
//構造体(久しぶり) struct point { int x, y; }; inline void IncPointMem(point* pt, int size, int point::*mp) {//第三引数がさっきのメンバ変数ポインタ for (int i = 0; i < size; ++i) { ++(pt[i].*mp); } } void IncX(point* pt, int size) {//第三引数がさっきの相対的なアドレス IncPointMem(pt, size, &point::x); } void IncY(point* pt, int size) {//第三引数がさっきの相対的なアドレス IncPointMem(pt, size, &point::y); } void Show(const point* pt, int size) { for (int i = 0; i < size; ++i) { cout << pt[i].x << ":" << pt[i].y << endl; } } #define ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) int main() { point pt[] = { {0,0}, {1,1}, {2,2}, {3,3}, }; int size = ARRAY_SIZE(pt); IncX(pt, size); IncY(pt, size); IncY(pt, size); Show(pt,size); system("pause"); }
//output
1:2
2:3
3:4
4:5
はい。こんな感じで出来ました。
int point::*mp = &point::y;
さっきのこれが仮引数と実引数で使われているんですね。
関数ポインタは結構手こずるなぁ。
ここまで。