仮想関数のアクセス指定子
どうやらC++はオーバーライドする際にアクセス指定子を好きなように書き換え放題らしい
具体的には
#include <iostream> class Base { public: virtual void func(){ std::cout << "Base" << std::endl; } }; class Deriverd : public Base { private: virtual void func(){ std::cout << "Deriverd" << std::endl; Base::func(); } }; int main() { Deriverd* p = new Deriverd(); Base* b = p; //p->func();//これは呼び出せない b->func(); return 0; }
こんな挙動をするって事
派生クラスでprivateにしたから安心! 外から呼ばれないね とか思ってると
基底クラスではpublicだったからポインタを介して呼ばれるというすごく嫌なことになりそう
どうやらC++の規格書によると、
・アクセス指定子は仮想関数をオーバーライドする際に特に制約はない
・アクセス制限のチェックは呼び出し時に唯一行われる(例え仮想関数であっても呼び出し時の静的な型による)
らしい。
たぶんこの仕様はコンパイラで仮想関数を実装する時に、各仮想関数の先頭に動的型に適合する関数へのジャンプコードを埋めるようなことができるように
するためなんだと思うんだけど(この実装なら仮想関数を実装するのに通常の関数呼び出しと、仮想関数の先頭への判定&ジャンプコード挿入でいけそう)
ちなみに今回上のコードを作って調べるまでは
「オーバーライドされる側のアクセス修飾子よりも範囲の広い物であればOK」だと思ってコード書いてました。
まあオーバーライドする関数がされる側よりも範囲の狭いアクセス修飾子つけちゃうとポインタを介して基底クラスの公開メソッドを呼べなくなるかなと思ってたので
まあ確かに仮想関数のアクセス制御は静的に、呼び出しは動的になんてしちゃえばこの問題は起きないけど・・・
他の言語はというと
Javaは今まで勘違いしてたように「オーバーライドされる側のアクセス修飾子よりも範囲の広い物であればOK」
C#はアクセス修飾子の変更は認めない らしい
まあC#の変更は認めない っていうのはoverrideでオーバーライドしたときのみ適用で
newで元メソッドを隠蔽した場合は好き勝手できるみたいだけど
なんかこう見てみるとやっぱりC#はJavaとC++のいいとこどりというか
Javaだとなんでもかんでも仮想関数扱いだから隠蔽できないし
C++だとデフォルトが非仮想だったり好き勝手に修飾子変えるれたりするし
その点C#ならoverrideとnewキーワードのおかげでその辺いろいろと柔軟性が出ていてよさそう
しかしC++に関してもVSが非仮想関数のオーバーライドしたときに赤の波線引いたりぐらいはしてくれても良いと思うんだけどなー