C++
 Computer >> コンピューター >  >> プログラミング >> C++

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();     // 引数なしで呼び出し
    return 0;
}

実行結果

In Derived, a=0

この出力結果から、実際に呼び出されたのは派生クラスDのs()であり、そこに渡されたのは基底クラスBのs()で定義されたデフォルト値「0」であることが分かります。

なぜこのような動作になるのか

理由は、デフォルト引数は関数のシグネチャに含まれないためです。シグネチャを構成するのは関数名・引数の型・個数・順序などであり、デフォルト値そのものは含まれません。そのため、基底クラスと派生クラスのs()は同一のシグネチャとみなされ、派生クラスのs()が基底クラスの仮想関数を正しくオーバーライドします。

デフォルト値はコンパイル時に決まる

デフォルト引数の値は、実行時ではなくコンパイル時に埋め込まれます。コンパイラは、関数呼び出しで引数が不足している箇所を検出すると、その式の静的な型(ここではポインタbの宣言型であるクラスB)に基づいてデフォルト値を補完します。

つまり上記のプログラムでは、「引数a = 0」という置換がコンパイル時に行われ、実行時には仮想関数の仕組みによって派生クラスDのs()が呼び出される、という流れになります。

注意点とベストプラクティス

この挙動は「どちらのクラスの既定値が使われるか分かりにくい」という問題を抱えており、バグの温床になりがちです。特に、基底クラスと派生クラスで異なるデフォルト引数を設定すると、呼び出し側の静的な型によって適用される既定値が変わり、予期しない結果を招きます。

そのため、Google C++スタイルガイドなどでも推奨されているように、仮想関数にはデフォルト引数を使用しないのが安全です。既定値が必要な場合は、NVI(Non-Virtual Interface)イディオムを活用し、デフォルト引数を持つ非仮想の公開関数から仮想関数を呼び出す設計にすると、挙動を明確に保てます。

  1. C#の1つのクラスに含められるデストラクタはいくつ?特徴と使い方を解説

    C#におけるデストラクタ(ファイナライザ)は、クラスが持つ特殊なメソッドの一つで、クラスのオブジェクト(インスタンス)を破棄する際に使用されます。結論から言うと、1つのクラス内に定義できるデストラクタは1つだけです。2つ以上宣言するとコンパイルエラーが発生します。C#のデストラクタの主な特徴デストラクタは引数(パラメータ)を受け取りません名前の先頭にチルダ記号(~)を付けて宣言します構造体(struct)の中ではデストラクタを定義できませんデストラクタは明示的に呼び出すことはできず、自動的に呼び出されますデストラクタは、オブジェクトの基底クラスのFinalizeメソッドを暗黙的に呼び出しますデ

  2. 【比較解説】C++とJavaでデフォルトの仮想動作はどう違う?

    ポリモーフィズム(多態性)を実現する「仮想関数」の扱いは、C++とJavaで大きく異なります。この違いを理解することは、両言語を使いこなす上で非常に重要です。 C++の場合:デフォルトは非仮想 C++では、クラスのメンバー関数はデフォルトで非仮想(non-virtual)です。これは、動的ディスパッチのオーバーヘッドを避け、パフォーマンスを優先する設計思想によるものです。仮想関数として動作させたい場合は、virtualキーワードを明示的に指定する必要があります。 class Base { public:    virtual void display_msg() { //