クラスを継承して、元(親)クラスのもつメソッドをオーバーロードすると、
例え(オーバーロード)引数の型が違っていても、オーバーライド扱いになり、
そのままでは、継承(子)クラス側から元(親)クラスのもつメソッドにアクセスできないという話。
Contents
用語の整理
私もよく混同するので、まずは言葉の整理から。
- オーバーロード:同名の関数であっても、引数の型が違っていれば(別のものとして)定義できる
- オーバーライド:継承元(親)クラスの持つメソッドを、継承(子)クラスで同名の別メソッドで上書きする
背景
ネットワークに関わる既存のライブラリがありました。
(redis の subscribe を司るライブラリ)
これを Qt で利用する際に、ステータスをタイマーで回して定期的に見に行くのがアレなので、
ステータスが変化(メッセージを受信)したら Qt signal を emit するように変更しようとしています。
Qt signal を扱いたいので既存のクラスを QObject の継承クラスにします。
元がネットワークのライブラリなので、connect という名のメソッドがありました。
察しの言い方はこの辺で既にピンときているかもしれません。
QObject にも signal と signal/slot を接続する connect という名のメソッドがあります。
前者は引数無し, 後者は引数を取ります。
継承した状態を念頭に考えると、オーバーロード状態なので問題なく併存できるだろうと。
例えば以下のようなコード。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <iostream> class testBase{ public: testBase(){;} virtual ~testBase(){;} virtual void testMethod(){ std::cout << "testMethod" << std::endl; } }; class testDeriv: public testBase{ public: testDeriv(){;} ~testDeriv(){;} void testMethod(int x) { std::cout << x << std::endl; } }; int main(void){ testDeriv* td=new testDeriv; td->testMethod(3); td->testMethod(); // ※ delete td; testBase* tb=new testDeriv; tb->testMethod(); // !! delete tb; return 0; } |
結論からいうと、「※」印の行だけがコンパイルエラーになります。
継承元(親)クラスの virtual は、あってもなくても結果は変わりません。
起こっていることの考察
継承(子)クラスを以下のようにすると通ります。
# ちなみにこれはベターな解ではありません。
1 2 3 4 5 6 7 |
class testDeriv: public testBase{ public: testDeriv(){;} ~testDeriv(){;} void testMethod(){ testBase::testMethod(); } void testMethod(int x) { std::cout << x << std::endl; } }; |
つまり、継承(子)クラス側から元(親)クラスの testMethod は確かに見えているはいるのです。
でも陽に定義してやらないと、アクセスできない、つまり存在しない。。。
恐らく以下のようなことが起こっているのではないかと思います。
継承(子)クラスのコンパイル時にまず(引数の型などを見ずに)オーバーライドのチェックをします。
元(親)クラスと同名のメソッドがいるので、オーバーライド判定となり、元(親)クラスのメソッドを引き継ぎません。
とにかく継承を先に済ませてから、オーバーロード(or 重複)判定をしていれば、最初の実装でも通りそうな気がします。
一方、元(親)クラスの同メソッドを virtual にしていても、
元(親)クラスのポインタでアクセスすると「!!」印の行は通ります。
これは継承(子)クラスの「引数の型を含めて」オーバーライドのチェックを行っているせいなのか、
或いは継承(子)クラスと同様に「引数の型を含めずに」オーバーライド判定を行った結果、
(引数の型を含めて)該当するメソッドがいないという結果から、
元(親)クラスのメソッドが呼ばれているのか。。。
引数を取らないメソッドは存在しないという旨のエラーは出ません。
ところで、下記のコードも「**」印の行が同様のコンパイルエラーで通りません。
1 2 |
testBase* tb=new testDeriv; tb->testMethod(3); // ** |
元(親)クラスの同メソッドを virtual にしていても、です。
なので、オーバーライド扱いになっているというわけでも無さそうです。
ちなみに以下のように明示的に override 宣言しても(すると?)、
オーバーライドしていないというエラーを吐くため、
確かにオーバーライド扱いにはなっていないようです。
1 2 3 4 5 6 |
class testDeriv: public testBase{ public: testDeriv(){;} ~testDeriv(){;} void testMethod(int x) override { std::cout << x << std::endl; } }; |
難しいですね、これらの現象を全て包括的に説明できる解釈が思いつきません。
名前の重複により隠されて、互いのクラスから他方のメソッドが見えていない状態に近いのでしょうか。
むむむ。。。
解決策
以下のように using を使って明示的に元(親)クラスの同メソッドを宣言してやると、
元々期待した通り、継承したメソッドをオーバーロードしたように動作します。
1 2 3 4 5 6 7 |
class testDeriv: public testBase{ public: testDeriv(){;} ~testDeriv(){;} using testBase::testMethod; void testMethod(int x){ std::cout << x << std::endl; } }; |