C++で基底クラスのオーバーロードされたメソッドが派生クラスで非表示になる理由と対処法
C++では、同じ名前でも引数の型や個数が異なる複数の関数を定義できる「関数オーバーロード」という便利な機能があります。しかし、継承を扱う際には注意が必要です。基底クラスでオーバーロードされたメソッドがあり、派生クラスでそのうちの1つを再定義すると、基底クラスに存在するその関数のすべてのオーバーロードバージョンが派生クラスから隠されてしまうのです。
これは「名前隠蔽(name hiding)」と呼ばれるC++の仕様によるもので、コンパイラはオーバーロード解決の前にスコープ内での名前検索を行い、派生クラスのスコープで該当する名前が見つかった時点で基底クラス側の検索を打ち切るためです。具体例で確認してみましょう。
コード例
#include <iostream>
using namespace std;
class MyBaseClass {
public:
void my_function() {
cout << "This is my_function. This is taking no arguments" << endl;
}
void my_function(int x) {
cout << "This is my_function. This is taking one argument x" << endl;
}
};
class MyDerivedClass : public MyBaseClass {
public:
void my_function() {
cout << "This is my_function. From derived class, This is taking no arguments" << endl;
}
};
int main() {
MyDerivedClass ob;
ob.my_function(10);
}一見すると、MyDerivedClass は MyBaseClass を継承しているため、引数付きの my_function(int) も呼び出せるように思えます。しかし、このプログラムをコンパイルするとエラーになります。
出力結果
[Error] no matching function for call to 'MyDerivedClass::my_function(int)' [Note] candidate is: [Note] void MyDerivedClass::my_function() [Note] candidate expects 0 arguments, 1 provided
エラーメッセージには「MyDerivedClass::my_function(int) を呼び出せる一致する関数が存在しない」と表示され、候補として挙げられているのは派生クラスで定義された引数なしの my_function() のみです。つまり、基底クラスの my_function(int) は継承されているにもかかわらず、呼び出し対象から完全に除外されているのです。
解決方法
この問題を回避するには、主に次の2つの方法があります。
1. using宣言を使う
派生クラス内で using宣言を記述すると、基底クラスのすべてのオーバーロードバージョンを派生クラスのスコープに取り込むことができます。
class MyDerivedClass : public MyBaseClass {
public:
using MyBaseClass::my_function; // 基底クラスの全オーバーロードを可視化
void my_function() {
cout << "This is my_function. From derived class" << endl;
}
};2. スコープ解決演算子を使う
呼び出し時に対象を明示的に指定したい場合は、スコープ解決演算子(::)で基底クラス名を修飾します。
ob.MyBaseClass::my_function(10);
まとめ
C++では、派生クラスで基底クラスのオーバーロード関数の一部を再定義すると、残りのオーバーロードも含めてすべて隠蔽されます。直感に反する動作であり、コンパイルエラーの原因になりやすいため、継承関係でオーバーロードを扱う場合は using宣言を活用して意図しない名前隠蔽を防ぐことをおすすめします。
-
C++の親クラス(基底クラス)から継承されるものとは?
オブジェクト指向プログラミングでは、親クラスの特性を子クラスに引き継ぐ「継承」という仕組みを利用できます。親クラスは基底クラス(base class)、子クラスは派生クラス(derived class)と呼ばれます。派生クラスは、基底クラスが持つデータメンバ(メンバ変数)やメンバ関数を継承することができます。アクセス指定子による継承の違い継承されるメンバへのアクセス可否は、アクセス指定子によって決まります。public:派生クラス、同じクラス内、クラス外のどこからでもアクセス可能です。protected:派生クラスと同じクラス内からのみアクセス可能です。クラス外からはアクセスできません。pri
-
C++のローカルクラスとは?特徴・制約と実装例をわかりやすく解説
ローカルクラスとは C++において、関数の中で宣言されたクラスは「ローカルクラス」と呼ばれます。これは、そのクラスが定義された関数の内部でのみ有効であるためです。 まず、ローカルクラスの基本的な構造を見てみましょう。 #include<iostream> using namespace std; void func() { class LocalClass { }; } int main() { return 0; } 上記の例では、関数 func() の中で LocalClass が定義されています。このように関数の内部で宣言されたクラスが、ローカルクラス