C++ではprivateな仮想関数をオーバーライドすることができる
バッドノウハウかもしれないので注意。
一瞬「あれ、おかしい」と思ったのでメモ。
Javaとは違うところ。
派生クラスでのオーバーライドが必要、かつ、派生クラスから直接呼び出す必要のない仮想関数を定義するのには便利かもしれない。
class Parent { public: virtual ~Parent() {} void method() {f();} private: virtual void f() {} }; class Child : public Parent { public: void f() override // OK { printf("hehehe"); } }; Child* c = new Child(); Parent* p = c; p->method(); //=>hehehe c->f(); // OK p->f(); // NG:'f' is a private member of 'Parent' delete p;
また、Javaでは基本クラスのメソッドの可視性を派生クラスで狭めることはできないが、C++では可能。
(publicなメソッドをオーバーライドしてprivateにするなど)
【C++の場合】
class Parent { public: virtual ~Parent() {} public: virtual void f() {} }; class Child : public Parent { private: void f() override // OK { printf("hehehe"); } }; Child* c = new Child(); Parent* p = c; p->f(); // OK c->f(); // NG
【Javaの場合】
class Parent { public void f() {} } class Child extends Parent { @Override private void f() {} // NG:スーパークラスでの定義 (public) より弱いアクセス特権を割り当てようとしました }
Javaの場合は、上記のように@Overrideアノテーションを使用すれば、
コンパイルエラーとして検出してくれるが、
privateなメソッドを誤ってサブクラスでオーバーライドしようとした場合に、
@Overrideアノテーションがない場合は、オーバーライドされずに別メソッドとして定義される。
【Javaの場合】
public class Parent { public void method() {f();} private void f() {System.out.println("parent f()");} } public class Child extends Parent { private void f() {System.out.println("child f()");} public static void main(String[] args) { Parent p = new Child(); p.method(); //=>parent f() コンパイルは通る。オーバーライドされていない。 } }
【考察】
なぜJavaではオーバーライドメソッドの可視性を狭めることができないのか。is-a関係として考えれば、Parentの一種であるChildが、Parentが持っている公開メソッドを非公開にできてはならない。Parentが「このメソッドはpublicである」と宣言した時点で、それはすべてのParent(子孫含む)においてpublicであることが保証されなければならない。
これは、Javaが動的型チェックを行う言語であるゆえ、守られなければならないことでもある。あるオブジェクトに対するメソッドコールは、JavaVMによって動的にチェックされる。この時、privateであるメソッドが外部オブジェクトから呼び出されてはならないのである。もし、仮に、スーパークラスのpublicメソッドがサブクラスによってprivateにオーバーライドされることがコンパイラによって許されたとしても、実行時にprivateメソッドが外部から呼ばれることをJavaVMは許してはならない。
javacは、JavaVMが許さないことを、プログラマに許さない仕組みでできている。
よって、これは正しい仕組みだと認識できる。一方で、C++は基本的に実行時の型チェックを行わない。
(dynamic_castなどの一部の機能を除いて)
そんなことをしたら遅くなるからだ。
設計コンセプトとして、C++は、危険を伴ってでも高速に動くようにできている。
publicなメンバ関数が、派生クラスでprivateにされたとして、かつ、そのprivateなメンバ関数が基底クラスのポインタを介して呼び出されたとしても、IllegalAccessExceptionのような例外は発生しないのだ。
その結果、引き起こされる挙動を正とするか誤とするかはプログラマ次第。
C++は(うまくコーディングすれば)高速に動き、かつ、プログラマに対する自由が大きい言語である。
本件に関しては、後者の特徴に当たる。
それ故、プログラマが負わなければならない責任も大きいと言えるのではないだろうか。