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

C++の仮想関数はどのように実装されているのか?仕組みとサンプルコードを解説

C++の仮想関数(virtual function)は、基底クラスのポインタを使って、派生クラスのオブジェクトの具体的な型を意識することなく、その派生クラスのメソッドを呼び出すために利用されます。これにより、基底クラスのポインタのリストを作成し、そこに格納されたさまざまな派生クラスのオブジェクトに対して、統一的な方法でメソッドを呼び出すことが可能になります。

仮想関数の大きな特徴は、コンパイル時ではなく実行時(ランタイム)に遅延解決(レイトバインディング)される点です。実際にどの関数が呼ばれるかは、ポインタが指しているオブジェクトの実際の型によって決まります。

仮想関数のサンプルコード

以下は、C++で仮想関数を実装したプログラムの例です。

#include <iostream>
using namespace std;

class B {
    public:
        virtual void s() { // 仮想関数
            cout << " In Base \n";
        }
};

class D : public B {
    public:
        void s() { // オーバーライド
            cout << "In Derived \n";
        }
};

int main(void) {
    D d;          // クラスDのオブジェクト
    B *b = &d;    // dを指すB*型のポインタ変数
    b->s();       // 派生クラスの関数が呼ばれる
    return 0;
}

実行結果

In Derived

この例では、ポインタbB*型ですが、実際に指しているのはクラスDのオブジェクトです。s()が仮想関数として宣言されているため、実行時にオブジェクトの実際の型が判定され、派生クラスDs()が呼び出されます。もしvirtualキーワードがなければ、基底クラスBs()が呼ばれることになります。

仮想関数の内部実装:vtableとvptr

では、仮想関数は内部的にどのように実現されているのでしょうか。その鍵となるのが仮想テーブル(vtable)仮想ポインタ(vptr)です。

仮想テーブル(vtable)

仮想関数を1つでも持つクラスには、コンパイラによって静的な仮想テーブルが生成されます。このテーブルには、そのクラスで呼び出し可能な各仮想関数へのポインタが格納されています。派生クラスで仮想関数をオーバーライドした場合、派生クラスのvtableには、オーバーライドされた関数のアドレスが設定されます。

仮想ポインタ(vptr)

仮想関数を持つクラスの各オブジェクトには、隠しメンバとしてvptrが埋め込まれます。このvptrは、自分のクラスのvtableを指しています。オブジェクトが生成される際、コンストラクタによってvptrが適切なvtableに設定されます。

動的ディスパッチの流れ

b->s()のような仮想関数の呼び出しは、次の手順で解決されます。

1. ポインタbが指すオブジェクト内のvptrを参照する。
2. vptrが指すvtableから、関数s()に対応するエントリを取り出す。
3. そのエントリに格納されたアドレスの関数を呼び出す。

この仕組みにより、ポインタの静的な型に関係なく、実行時にオブジェクトの実際の型に応じた関数が呼び出されます。これが動的ディスパッチ(動的結合)と呼ばれる仕組みで、ポリモーフィズムを実現する基盤となっています。

まとめ

仮想関数は、基底クラスのポインタや参照から、実際のオブジェクトの型に応じた派生クラスのメソッドを呼び出すための機能です。内部的にはvtableとvptrによって実装され、関数呼び出しが実行時に解決されることで、柔軟で拡張性の高いオブジェクト指向設計を可能にしています。なお、動的解決のためにわずかな実行時オーバーヘッド(vtable参照のコスト)が発生する点も、パフォーマンスを意識する際には覚えておくとよいでしょう。

  1. C++で関数にオブジェクトを渡す5つの方法を徹底解説!値渡しからポインタ渡しまで

    C++では、オブジェクトを関数へ渡す方法が複数あります。ここでは、クラス X を関数 fun に渡す場合を例に、代表的な渡し方とそれぞれの特徴・使い分けのポイントを解説します。用途に応じて適切な方法を選ぶことで、パフォーマンスやコードの安全性を高めることができます。 1. 値渡し(Pass by Value) オブジェクトの浅いコピー(シャローコピー)が関数のスコープ内に作成されます。関数内でどのような変更を行っても、呼び出し元のオブジェクトには一切影響しません。独立した作業用コピーが必要な場合に有効ですが、大きなオブジェクトの場合はコピーのコストに注意が必要です。 宣言 void fun(

  2. C#の仮想関数(virtualメソッド)とは?基本と使い方を実例付きで解説

    仮想関数(virtualメソッド)とは C#のvirtualキーワードは、メソッド、プロパティ、インデクサー、イベントを修飾するために使用されます。基底クラスで定義した関数を、それを継承する派生クラスで独自に実装し直したい場合に、その関数を「仮想関数」として宣言します。 仮想関数は、継承先のクラスごとに異なる実装を持つことができ、実際にどの実装が呼び出されるかは実行時に決定されます。この仕組みによって、オブジェクト指向プログラミングにおける重要な概念であるポリモーフィズム(多態性)が実現されます。 仮想関数の宣言方法 仮想関数は、メソッドの戻り値の型の前に「virtual」キーワードを付ける