C++のデフォルト引数と仮想関数の仕組みを解説
C++では、仮想関数とデフォルト引数を組み合わせた場合、一見すると直感に反する挙動が発生することがあります。本記事では、具体的なサンプルプログラムをもとに、その仕組みと理由をわかりやすく解説します。
サンプルコード
#include<iostream>
using namespace std;
class B {
public:
virtual void s(int a = 0) {
cout<<" In Base \n";
}
};
class D: public B {
public:
virtual void s(int a) {
cout<<"In Derived, a="<<a;
}
};
int main(void) {
D d; // クラスDのオブジェクト
B *b= &d;// d を指す B* 型のポインタ
b->s();// 派生クラスの s() が呼ばれる
return 0;
}
実行結果
In Derived, a = 0
この実行結果から、派生クラスの s() が呼び出されているにもかかわらず、基底クラスの s() に設定されたデフォルト値(a = 0) が使用されていることがわかります。一見矛盾しているように見えますが、これには明確な理由があります。
なぜこのような挙動になるのか
1. デフォルト引数は関数のシグネチャに含まれない
デフォルト引数は、関数のシグネチャ(識別情報)の一部として扱われません。そのため、基底クラスの s(int) と派生クラスの s(int) は同じシグネチャを持つとみなされ、基底クラスの s() は派生クラスによってオーバーライドされます。
2. デフォルト値はコンパイル時に適用される
デフォルト引数の置き換えはコンパイル時(静的バインディング)に行われます。コンパイラは、関数呼び出しで引数が省略されていることを検出すると、その場でデフォルト値を埋め込みます。ここで重要なのは、参照されるのが「ポインタの静的な型」側の宣言だという点です。
一方、「実際にどの関数を呼び出すか」は、仮想関数の仕組みにより実行時(動的バインディング)で決定されます。
したがって、上記のプログラムは次のような流れで動作します。
- コンパイル時: ポインタ
bの静的な型はB*であるため、基底クラスBの宣言にあるデフォルト値a = 0が呼び出しに埋め込まれます。 - 実行時: 実際のオブジェクトは
D型であるため、仮想関数のディスパッチによって派生クラスのs()が呼び出されます。
この「コンパイル時」と「実行時」の処理のズレこそが、派生クラスの関数に基底クラスのデフォルト値が渡されてしまう原因です。
まとめと注意点
- 呼び出される関数の決定 → 実行時(オブジェクトの動的な型に基づく)
- デフォルト引数の適用 → コンパイル時(ポインタの静的な型に基づく)
こうした不一致によるバグを避けるためには、仮想関数にデフォルト引数を指定しないことが一般的なベストプラクティスとされています。有名な技術書『Effective C++』でも「仮想関数のデフォルト引数を再定義してはならない」ことが推奨されています。どうしても既定値が必要な場合は、NVI(Non-Virtual Interface)イディオムのように、非仮想関数から仮想関数を呼び出す設計を検討するとよいでしょう。
-
C++のフレンド関数とフレンドクラスの基本を解説
C++におけるフレンド関数(friend function)とは、クラスのスコープ外で定義される関数でありながら、そのクラスのすべてのprivateメンバーおよびprotectedメンバーにアクセスする権限を持つ特別な関数です。フレンド関数のプロトタイプ(宣言)はクラス定義の中に記述しますが、あくまで「友達」として扱われるだけであり、クラスのメンバー関数にはなりません。この点は重要なポイントです。フレンドとして指定できるものフレンドとして宣言できるのは、以下のようなものです。通常の関数関数テンプレート他のクラスのメンバー関数クラス全体(この場合、そのクラスのすべてのメンバーがフレンドになります
-
【比較解説】C++とJavaでデフォルトの仮想動作はどう違う?
ポリモーフィズム(多態性)を実現する「仮想関数」の扱いは、C++とJavaで大きく異なります。この違いを理解することは、両言語を使いこなす上で非常に重要です。 C++の場合:デフォルトは非仮想 C++では、クラスのメンバー関数はデフォルトで非仮想(non-virtual)です。これは、動的ディスパッチのオーバーヘッドを避け、パフォーマンスを優先する設計思想によるものです。仮想関数として動作させたい場合は、virtualキーワードを明示的に指定する必要があります。 class Base { public: virtual void display_msg() { //