C++クラスの書き方が面倒くさい訳
Cが全然分からないころの話。
javaのように1クラス1ファイルに書かないのはなんで?
メンバがポインタばっかりなのはなんで?
という疑問を持ちながらコーディングしてるともやもやして嫌でした。
もやもやがいやなのでjava風に書いていたら当然のように詰まりました。
ポインタじゃなかったらメモリが大変なことになるだろうが!
というのは最もなんですけど、じゃ小さいプログラムならありなのか?スマートポインタ使えばいいのか?と言うとそうでもない。
今日書いてみたいのは、クラスは相互参照がめんどくせぇから分割しろ。そして同じ理由でメンバ変数はポインタにしとこうぜ、というお話。
相互参照の解決
AクラスがBクラスを使って、BクラスがAクラスを使う。
こういう状態を相互参照とか相互includeとか言うと思う。
javaは何でもないことだけど、C++はこれを簡単には許してくれない。
相互参照の解決方法には前方宣言がある。
具体的にはこういうのが通らない。
//Foo.h============================ #include "Bar.h" class Foo{ Bar* bar; }; //Bar.h============================ #include "Foo.h" class Bar{ Foo* foo; }; //main.cpp============================ #include "Foo.h"//FooでもBarでもいい int main(void){ return 0; }
通らない理由は両ヘッダが相互にincludeしているからだ。
includeは指定したテキストをそこに書いているだけのアナログな処理だ。
下のコードの場合はBarクラスがエラーになる。読み込み順序が逆ならFooがエラーになる。
#include "Bar.h" class Foo{ Bar* bar; }; //↑↑↑↑↑↑↑↑↑↑ //上は下と同じこと //↓↓↓↓↓↓↓↓↓↓ class Bar{ Foo* foo;//この時点でBarは"定義"されておらず"宣言"すらされていないのでNG };//Barの"定義"完了 class Foo{ Bar* bar;//この時点でBarは"定義"されているのでOK };//Fooの"定義"完了
どちらのクラスも相手が先に"宣言"済みでないとコンパイルできないがそれは無理な話。
これを解決するために前方"宣言"がある。
前方宣言する
具体的にはこうする必要がある
//Foo.h============================ //#include "Bar.h" class Bar;//includeしないで前方"宣言" class Foo{ Bar* bar; }; //Bar.h============================ //#include "Foo.h" class Foo;//includeしないで前方"宣言" class Bar{ Foo* foo; };
これだと問題なくコンパイルできる。
気をつけるのは
・includeではなく前方宣言すること
クラスをインスタンス化するためには"定義"が必要だが、しないならそういう名前のクラスが存在することが分かればいい。
そのためにとりあえず"宣言"だけするのが前方"宣言"。
ポインタじゃないと
//Foo.h============================ class Bar; class Foo{ Bar bar;//←ポインタ型でない }; //Bar.h============================ class Foo; class Bar{ Foo foo;//←ポインタ型でない };
これが通らない。
通らない理由は"宣言"だけではインスタンス化できないからだ。
通したければヘッダを読み込む必要が出てくるのだが、相互参照している場合はそれができない。
だからメンバ変数はポインタがいいんじゃないかな、と。
実装と定義を分ける理由も結局は相互参照が面倒くさすぎるからかなーと思います。
//これは通らない //Foo.h============================ class Bar; class Foo{ Bar* bar; public: void baz(void){ bar->fiz();//宣言だけではどんな関数があるかは分からないし…… } }; //Bar.h============================ class Foo; class Bar{ Foo* foo; public: void fiz(void){ foo->baz();//宣言だけではどんな関数があるかは分からないし…… } }; //これは通る //Foo.h============================ class Bar; class Foo{ Bar* bar; public: void baz(void); }; //Bar.h============================ class Foo; class Bar{ Foo* foo; public: void fiz(void); }; //両方とも定義終了!ここから実装 //Foo.cpp============================ #include "Foo.h" #include "Bar.h" void Foo::baz(void){ bar->fiz(); } //Bar.cpp============================ #include "Bar.h" #include "Foo.h" void Bar::fiz(void){ foo->baz(); }
javaはそんなの関係ないから楽ですね。