ストラウストラップのプログラミング入門(23) 14.3.3「オーバーライド」補遺
『ストラウストラップのプログラミング入門』を読む。今日は、14章の補遺です。
前回の読書日記にいただいたコメントを読んで、14.3.3「オーバーライド」を読み返しました。
ずっと考えていたら、オーバーライドがゲシュタルト崩壊してきたので、ちょっと文章で書いてみます。正直まだよく理解してない感があります。
こういうコードがあるとして。
#include <iostream> #include "../../std_lib_facilities.h" struct B{ virtual void f() const { std::cout << "B::f " << std::endl; } void g() const { std::cout << "B::g " << std::endl; } // 仮想関数でない }; struct D : B{ void f() const { std::cout << "D::f " << std::endl; } // B::f()をオーバーライド void g() { std::cout << "D::g " << std::endl; } }; struct DD : D{ void f() { std::cout << "DD::f " << std::endl; } // D::f()をオーバーライドしない void g() const { std::cout << "DD::g " << std::endl; } }; void call (const B& b) { b.f(); b.g(); } int main() { B b; D d; DD dd; std::cout << "BのインスタンスをBクラスのオブジェクトとして扱う: \n"; call(b); // (0) std::cout << "DのインスタンスをBクラスのオブジェクトとして扱う: \n"; call(d); // (1) std::cout << "DDのインスタンスをBクラスのオブジェクトとして扱う: \n"; call(dd); // (2) std::cout << "Bのインスタンスを使う: \n"; b.f(); // (3.1) b.g(); // (3.2) std::cout << "Dのインスタンスを使う: \n"; d.f(); // (4.1) d.g(); // (4.2) std::cout << "DDのインスタンスを使う: \n"; dd.f(); // (5.1) dd.g(); // (5.2) keep_window_open(); }
実行結果。
BのインスタンスをBクラスのオブジェクトとして扱う B::f // (0.1) B::g // (0.2) DのインスタンスをBクラスのオブジェクトとして扱う D::f // (1.1) B::g // (1.2) DDのインスタンスをBクラスのオブジェクトとして扱う D::f // (2.1) B::g // (2.2) Bのインスタンスを使う B::f // (3.1) B::g // (3.2) Dのインスタンスを使う D::f // (4.1) D::g // (4.2) DDのインスタンスを使う DD::f // (5.1) DD::g // (5.2)
ここで、(1.2)と(2.2)の箇所が、「クラスにバインドされた非仮想関数が呼び出された結果」である、という理解をしています。
最初は、「call関数の引数にconstがあるから、関数宣言にconstがないD::g()でなく、constがあるスーパークラスのB::g()が呼び出された」のだと誤解していました。そうではないのですね。その理屈に従うのであれば、B::g()とDD::g()は戻り値も引数もconstの有無も同じ宣言ですから、(2.2)でDD:gが表示されて然るべきです。しかし、表示されたのは相変わらず B::gです。
(2.1)がDD::fでなくD::fになる部分こそが、「call関数の引数にconstがあるから、関数宣言にconstがないDD::f()ではなく、constがあるスーパークラスのD::f()が呼び出された」という話ですね。実際、D::f()の定義で、constを外すと、B::f()が出力されます。インスタンスにバインドされた関数が呼び出されます。
言語仕様を帰納法で理解しようとするなという話ですが、、、ひとまずこんな感じです。
で、分からないのは、「(4.2)が、B::gでなく、D::gなのは、const呼び出しではないためなのか?」ということ。
- 仮想関数は、constが欠けるとオーバーライドしない。(例)DD::f()は、D::f()をオーバーライドしない。
- 非仮想関数は、constが欠けていても、オーバーライドする。(例)D::g()は、B::g()をオーバーライドする。
- ただし、const呼び出しが行われた場合は、それ相応の関数が呼び出される。
ということでしょうか。。。
根本的な問題として
検索しても、なかなか、なるほどネ!という解説が見つからないのですが・・・
冷静に考えて、複数の問題を同時に考えて、ややこしくしているだけでは?
- 関数定義の違いによるオーバーライドの可否
- const呼び出しとそうでない呼び出しの違い
- 仮想関数と非仮想関数で呼び出される
たいへん情けないので、もうちょい考えることに。