C++で派生クラスのメソッドに基底クラスより厳しいアクセス制限を付けるとどうなるか
この記事では、C++における興味深いトピックである「派生クラスのメソッドに、基底クラスよりも厳しいアクセス制限を付けた場合の挙動」について解説します。具体的なコード例とその出力結果を通じて、アクセス指定子がどのように機能するのかを詳しく見ていきましょう。
例:派生クラスでメソッドをprivateにした場合
まずは以下のコードを見てください。基底クラスではpublicとして宣言されている仮想関数display()を、派生クラスではprivateとしてオーバーライドしています。
#include <iostream>
using namespace std;
class BaseClass {
public:
virtual void display(){
cout << "Print function from the base class" << endl;
}
};
class DerivedClass: public BaseClass {
private:
void display() {
cout << "Print function from the derived class" << endl;
}
};
int main() {
}このコード自体はコンパイルエラーになりません。しかし、main関数の中身を次のように書き換えると、エラーが発生します。
int main() {
DerivedClass d;
d.display();
}出力(コンパイルエラー)
main.cpp: In function ‘int main()’:
main.cpp:20:15: error: ‘virtual void DerivedClass::display()’ is private
within this context
d.display();
^
main.cpp:13:10: note: declared private here
void display() {
^~~~~~~このように、派生クラスのメソッドがprivateとして宣言されているため、直接呼び出そうとするとコンパイルエラーになります。これはC++のアクセス制御が正しく機能している例です。
基底クラスのポインタ経由で呼び出すとどうなるか
次に、同じ構成のまま、関数を基底クラスのポインタ経由で呼び出してみましょう。すると、驚くことにプログラムは正常に動作します。
#include <iostream>
using namespace std;
class BaseClass {
public:
virtual void display(){
cout << "Print function from the base class" << endl;
}
};
class DerivedClass: public BaseClass {
private:
void display() {
cout << "Print function from the derived class" << endl;
}
};
int main() {
BaseClass *b = new DerivedClass;
b->display();
}出力
Print function from the derived class
このプログラムでは、privateとして宣言されているはずのDerivedClass::display()が、基底クラスのポインタを通じて呼び出されています。それでもプログラムは問題なく動作します。なぜでしょうか。
理由:アクセスチェックはコンパイル時に行われる
ポイントは、C++のアクセス指定子のチェックはコンパイル時に行われるという点です。
- コンパイル時には、ポインタbの静的な型(BaseClass)に対してアクセス可能性が検証されます。BaseClass::display()はpublicなので、この呼び出しは合法と判断されます。
- 実行時には、ポインタが実際に指しているオブジェクトの型(DerivedClass)に対応する関数、つまりオーバーライドされたDerivedClass::display()が仮想関数の仕組みによって呼び出されます。このときアクセス指定子は再確認されません。
その結果、派生クラスのprivateな関数が、基底クラスのポインタを経由して間接的に呼び出されることになるのです。
まとめ
派生クラスでオーバーライドしたメソッドのアクセスレベルを狭めても、多態性(ポリモーフィズム)を使った呼び出しによってその制限を回避できてしまう場合があります。これはC++の仕様上の挙動であり、アクセス指定子は「カプセル化のための設計上の指針」であり、実行時のセキュリティ機構ではないことを理解しておくことが重要です。意図しない呼び出しを防ぎたい場合は、設計段階でインターフェースの公開範囲を慎重に検討しましょう。
-
C++のドット演算子(.)とは?矢印演算子との違いと使い分けを解説
C++では、クラスのメンバーにアクセスするために「ドット演算子(.)」と「矢印演算子(->)」の2種類が用意されています。どちらも同じ目的で使われますが、使用する場面が異なります。C++において、class・struct・unionとして宣言された型はすべて「クラス型」とみなされます。以下では、これらに対する両演算子の使い方を説明します。ドット演算子(a.b)a.b は、b がオブジェクト a(またはオブジェクトへの参照[1])のメンバーである場合にのみ使用できます。つまり、a.b の形式では、a は必ずクラス型の実際のオブジェクト(またはその参照)である必要があります。矢印演算子(a-
-
C++のアクセス修飾子とは?public・private・protectedの違いをわかりやすく解説
データ隠蔽(Data Hiding)は、オブジェクト指向プログラミング(OOP)における重要な機能の一つです。これにより、プログラム内の関数がクラス型の内部表現に直接アクセスすることを防ぎ、クラスの内部構造を保護できます。 クラスメンバーへのアクセス制限は、クラス本体内で指定する「アクセス修飾子」と呼ばれるラベル、すなわち public、private、protected の3つのセクションによって制御されます。 デフォルトのアクセスレベル C++では、クラスのメンバーおよびクラス自体のデフォルトのアクセスレベルは private です。アクセス修飾子を明示しない場合、メンバーは自動的に非公開