Sample sample;
というクラスメンバで、メンバイニシャライザを使わないとパラメタなしでコンストラクタが呼ばれる。
メンバイニシャライザで
sample("ABC")
と記述すると、パラメタ"ABC"でコンストラクタが呼ばれる。
メンバイニシャライザを使わないでコンストラクタで
sample = "ABC";
と記述するとパラメタ"ABC"で代入が呼ばれる。
パラメタなしのコンストラクタ + 代入 よりは パラメタありのコンストラクタ の方が呼び出す回数が1回減るというのが唯一の利点だろうか? パラメタありのコンストラクタが パラメタなしのコンストラクタ + 代入 に比べどれだけ効率よく書けるというのだろう。
実際にクラスを書くと多くの場合、パラメタありのコンストラクタ = パラメタなしのコンストラクタ + 代入 になってしまうのではなかろうか。パラメタありのコンストラクタをそうならないように実装するのはメンテナンス性を考えると、あまり良いことではない。基本的に1か所で処理すれば良いことを分散させるのは著しくメンテナンス性を下げる。
メンバイニシャライザに限らず、変数を宣言するときに、
Sample sample1; sample1 = "ABC";
の場合と
Sample sample2("ABC");
の場合の違い。前者はパラメタなしでコンストラクタが呼ばれた後で代入が呼ばれる。
C++は明示的に初期値を設定することは強制されない。例えばOCamlではインスタンス生成時メンバの値は必ず設定しないとコンパイルを通してもらえない。OCamlはメンバイニシャライザ的なものは特別な文法でなく、単に型(レコード)の初期化。とにかく初期値を書かねばコンパイルが通らない。
C++は初期化の記述が強制されないにも関わらず、メンバイニシャライザがなければパラメタなしのコンストラクタが勝手に呼ばれる。これがなんともバランスが悪い。
インスタンス生成時コンストラクタが呼ばれるってのはC++のキモなので外せないのはわかるが、Cの構造体の単にメモリを確保するだけという放置っぷりとはかなり違う。Cの放置っぷりに比べC++で勝手にコンストラクタが呼ばれるのがバランスとか気持ちとかが悪い。勝手にコンストラクタが呼ばれるのがクラスのみで、基本型は結局放置ってのもどうよ。ここまでするんなら、暗黙にでなく初期化を書くことを強制すれば良かったのではないかと思うが後知恵。初期化忘れのバグは今日も作られ続ける。変数の初期化を強制する言語が主流になるのは、まだまだ先かな。C++はCのソースを概ねそのまま生かすという選択をしたから無理だったのかもしれない。80年代とか90年代にはこんなRust的な考えはまだ早かっただろう。初期値を要求するRustに対し、Goは基本型に初期値があるという優しいアプローチになっている。
クラスメンバをポインタで持つ場合、暗黙にコンストラクタは呼ばれない。コンストラクタなどでnewとかNULLの代入などを書かなくてはいけないが、強制はされない。std::stringなど標準クラスがポインタでなくても使えるので、メンバ全部ポインタってのは「敢えて」そうやってるプロジェクトに限るだろう。
Objective-Cのクラスは基本的に皆ポインタで扱う。その考え方を取り入れるとC++で敢えてクラス全部ポインタで扱うという方向になるのではないか。まぁ、そういうやり方でも構築できる、そうでないやり方もあり、ってのがC++の特徴なのだろう。GUI部品のクラスは大抵OSレベルでポインタなのでポインタで扱うのが普通だ。暗黙のコンストラクタを避けるために、というわけではない。そういえば昔のMacintoshや16bitのWindowsはメモリの連続領域を確保する目的で、ポインタのポインタをハンドルと名付けて扱ってた。あぁ話がそれた。
2023.09.25 - 2024.01.11
結局のところ、コンストラクタが2個以上になる場合、メンテナンス性を考えると初期化を1か所にまとめるのが妥当な選択となる。これはメンバイニシャライザを使わないという選択になる。言語仕様がメンテナンス性に反するのだから両立はできない。ループの底で大量にインスタンスが作られるようなクラスは軽くなるように頑張るのも無駄ではないが、多くのクラスはそんなことは要求されない。
2024.08.11
OSTRACISM CO.
OSTRA / Takeshi Yoneki