C++の仮想関数について

AI実装検定のご案内

C++の仮想関数とは、基底クラスのポインタや参照を通じて関数を呼び出したときに、実際のオブジェクトの型に応じて呼び出される関数が決まる仕組みです。

通常のメンバ関数では、呼び出される関数は変数やポインタの型をもとに決まります。

一方、仮想関数では、ポインタや参照の型ではなく、実際に指しているオブジェクトの型に応じて処理が切り替わります。

この仕組みによって、基底クラスを共通のインターフェースとして使いながら、派生クラスごとに異なる動作を実現できます。

たとえば、「動物」という基底クラスに「鳴く」という処理を用意し、「犬」「猫」「鳥」といった派生クラスでそれぞれ異なる鳴き方を定義するような場面で使われます。

目次

仮想関数が必要になる理由

共通の型で異なるオブジェクトを扱える

仮想関数が役立つ代表的な場面は、複数の派生クラスを基底クラスとしてまとめて扱いたい場合です。

たとえば、犬、猫、鳥をすべて「動物」として扱い、それぞれに対して同じ「鳴く」という操作を行うとします。

このとき、仮想関数を使えば、呼び出す側は具体的な型を意識せずに処理できます。

呼び出す側は「動物として鳴かせる」とだけ考えればよく、実際には犬なら犬の鳴き方、猫なら猫の鳴き方、鳥なら鳥の鳴き方が実行されます。

このように、同じ操作で異なる動作を実現できることを、オブジェクト指向ではポリモーフィズムと呼びます。

実行時に具体的な型が決まる場合に便利

プログラムでは、コンパイル時には具体的な型が分からず、実行時の条件によって扱うオブジェクトが変わることがあります。

たとえば、設定ファイルの内容、ユーザーの入力、外部データ、プラグインなどによって、実行時にどのクラスのオブジェクトを使うかが決まる場合です。

このような場面では、仮想関数を使うことで、呼び出し側のコードを大きく変えずに処理を切り替えられます。

仮想関数と通常の関数の違い

通常の関数は静的バインディングされる

通常のメンバ関数では、どの関数を呼び出すかがコンパイル時に決まります。

この仕組みを静的バインディングといいます。

基底クラス型のポインタで派生クラスのオブジェクトを指していたとしても、その関数が仮想関数でなければ、ポインタの型に基づいて基底クラス側の関数が呼ばれます。

つまり、実際のオブジェクトが派生クラスであっても、呼び出し側の型が基底クラスであれば、基底クラスの関数が実行されます。

仮想関数は動的バインディングされる

仮想関数では、どの関数を呼び出すかが実行時に決まります。

この仕組みを動的バインディングといいます。

基底クラス型のポインタや参照を通じて関数を呼び出した場合でも、実際のオブジェクトが派生クラスであれば、派生クラス側でオーバーライドされた関数が呼ばれます。

この点が、通常のメンバ関数と仮想関数の大きな違いです。

オーバーライドとは

基底クラスの仮想関数を派生クラスで再定義すること

オーバーライドとは、基底クラスで定義された仮想関数を、派生クラスで再定義することです。

たとえば、基底クラスに「表示する」という仮想関数があり、派生クラスでその表示内容を変更する場合、派生クラス側の関数は基底クラスの関数をオーバーライドしていることになります。

オーバーライドによって、共通の関数名を使いながら、クラスごとに異なる処理を実行できます。

override はミスを防ぐために使う

C++では、派生クラスで仮想関数をオーバーライドするときに、override を付けることができます。

override は、その関数が基底クラスの仮想関数を正しくオーバーライドしているかどうかを、コンパイラにチェックさせるための指定です。

関数名のスペルミス、引数の違い、const の付け忘れなどがあると、本来オーバーライドしたつもりでも、実際には別の関数として扱われてしまうことがあります。

override を付けておけば、そのようなミスをコンパイル時に検出できます。

そのため、派生クラスで仮想関数を上書きする場合は、基本的に override を付けるのが望ましいです。

override は関数を仮想関数にするものではない

override は、関数を仮想関数にするためのものではありません。

仮想関数にする役割を持つのは、基底クラス側の virtual です。

override はあくまで、「この関数は基底クラスの仮想関数をオーバーライドしているはずなので、違っていたらエラーにしてください」という意味の指定です。

そのため、基底クラス側の関数が仮想関数でない場合、派生クラス側で override を付けることはできません。

純粋仮想関数とは

派生クラスに実装を求める仮想関数

純粋仮想関数とは、基底クラスで「この関数は派生クラス側で具体的に定義してください」と示すための仮想関数です。

純粋仮想関数を持つクラスは、抽象クラスになります。

抽象クラスは、そのままオブジェクトとして生成することはできません。

具体的なオブジェクトとして使うには、派生クラス側で必要な仮想関数を実装する必要があります。

純粋仮想関数を持つクラスはインターフェースとして使われる

純粋仮想関数を持つクラスは、共通の操作だけを定義するインターフェースとしてよく使われます。

たとえば、「描画できるもの」「実行できるコマンド」「保存できるデータ」といった抽象的な概念を基底クラスで表し、具体的な処理は派生クラスで実装します。

このようにすることで、呼び出し側は具体的なクラスを知らなくても、共通の操作だけを使って処理できます。

純粋仮想関数にも定義を書ける

純粋仮想関数は、一般的には「実装を持たない関数」と説明されることがあります。

しかし、C++として厳密には、純粋仮想関数にも定義を書くことができます。

つまり、純粋仮想関数は「必ず実装を持てない関数」ではありません。

より正確には、純粋仮想関数とは、クラスを抽象クラスにし、具象クラスとして使う派生クラスにオーバーライドを求める仮想関数です。

ただし、初心者向けの理解としては、「派生クラスで具体的な処理を実装するための関数」と捉えると分かりやすいです。

派生クラスも抽象クラスになる場合がある

基底クラスに純粋仮想関数がある場合、派生クラスがそれをオーバーライドしなければ、その派生クラスも抽象クラスになります。

つまり、すべての派生クラスが必ずすぐに実装しなければならないわけではありません。

ただし、そのクラスを実際にオブジェクトとして生成したい場合は、継承している純粋仮想関数をすべて実装する必要があります。

仮想デストラクタとは

基底クラス経由で削除するなら必要

仮想関数を使うクラスでは、デストラクタも仮想にすることが多いです。

特に、基底クラスのポインタを通じて派生クラスのオブジェクトを削除する可能性がある場合、基底クラスのデストラクタは必ず仮想にする必要があります。

基底クラスのデストラクタが仮想でない状態で、基底クラス型のポインタから派生クラスのオブジェクトを削除すると、C++では未定義動作になります。

これは単に「派生クラスのデストラクタが呼ばれないかもしれない」という程度の問題ではなく、プログラム全体の動作が保証されない危険な状態です。

ポリモーフィズム用の基底クラスでは仮想デストラクタを用意する

ポリモーフィズムのために使う基底クラスでは、基本的に仮想デストラクタを用意するのが安全です。

これにより、基底クラス型のポインタやスマートポインタを通じてオブジェクトを破棄しても、派生クラスのデストラクタが正しく呼ばれます。

特に、std::unique_ptr などで基底クラス型として派生クラスのオブジェクトを管理する場合も、基底クラスのデストラクタが仮想であることが重要です。

基底クラス経由の削除を禁止する設計もある

例外的に、基底クラスのデストラクタを仮想にせず、さらに外部から削除できないようにする設計もあります。

たとえば、基底クラス経由でオブジェクトを削除させたくない場合、デストラクタを外部から呼べないように設計することがあります。

ただし、一般的なポリモーフィズム用の基底クラスでは、仮想デストラクタを用意するのが分かりやすく安全です。

仮想関数の内部的な仕組み

多くの実装では vtable と vptr が使われる

C++の仮想関数は、多くのコンパイラ実装では、vtablevptr という仕組みによって実現されています。

ただし、これはC++標準で厳密に決められている内部構造ではありません。

C++標準が定めているのは、仮想関数がどのように振る舞うべきかというルールです。

内部でどのように実装するかは、コンパイラや環境によって異なる可能性があります。

そのため、vtable や vptr は「多くの実装で使われている代表的な仕組み」と理解するとよいです。

vtable は仮想関数の呼び出し先を管理する表

vtable は、仮想関数の呼び出し先を管理するための表のようなものです。

基底クラスに仮想関数がある場合、そのクラスに対応する vtable が用意されます。

派生クラスが仮想関数をオーバーライドすると、派生クラス側の vtable では、その関数の呼び出し先が派生クラスの関数に置き換わります。

これにより、基底クラス型のポインタから呼び出しても、実際のオブジェクトに対応した関数が実行されます。

vptr はオブジェクトから vtable を参照するための情報

vptr は、オブジェクト内部に持たれる vtable へのポインタのような情報です。

仮想関数を持つクラスのオブジェクトには、多くの実装で vptr が追加されます。

仮想関数を呼び出すと、実行時にこの vptr を通じて適切な vtable が参照され、そこから呼び出すべき関数が決定されます。

ただし、多重継承や仮想継承が絡むと、この仕組みはより複雑になります。

初心者向けには、単一継承を前提に「vptr が vtable を指している」と理解すると十分です。

仮想関数のメリット

柔軟で拡張しやすい設計にできる

仮想関数を使うと、呼び出し側のコードを変更せずに、派生クラスを追加しやすくなります。

たとえば、図形を描画する処理で、円や四角形を扱っていたとします。

あとから三角形を追加したくなった場合でも、共通の基底クラスに従って新しい派生クラスを作れば、既存の処理を大きく変更せずに対応できます。

このように、拡張しやすい設計を作れる点が仮想関数の大きなメリットです。

呼び出し側が具体的な型を知らなくてよい

仮想関数を使うと、呼び出し側は具体的な派生クラスを知らなくても処理できます。

たとえば、「保存する」「描画する」「実行する」といった共通の操作だけを知っていれば、実際のクラスが何であるかを意識せずに扱えます。

これにより、コードの依存関係を減らし、変更に強い設計にしやすくなります。

実行時ポリモーフィズムを実現できる

仮想関数は、実行時ポリモーフィズムを実現するための代表的な仕組みです。

実行時ポリモーフィズムとは、プログラムの実行中に、実際のオブジェクトの型に応じて処理を切り替える仕組みです。

プラグイン機構、GUI部品、ゲームオブジェクト、コマンド処理、ファイル形式ごとの処理など、さまざまな場面で利用されます。

仮想関数のデメリット

実行時コストがある

仮想関数は、通常の関数呼び出しに比べて、呼び出し先を実行時に決めるためのわずかなコストがあります。

多くの一般的なアプリケーションでは問題にならない程度ですが、非常に高頻度で呼び出される処理や、リアルタイム性が重要な処理では注意が必要です。

オブジェクトサイズが増えることがある

多くの実装では、仮想関数を持つクラスのオブジェクトに vptr が追加されます。

そのため、オブジェクトのサイズが増えることがあります。

特に、大量の小さなオブジェクトを扱う場合は、メモリ使用量に影響する可能性があります。

インライン化されにくい場合がある

通常の関数であれば、コンパイラが処理をインライン展開しやすい場合があります。

しかし、仮想関数は呼び出し先が実行時に決まるため、一般的にはインライン化されにくくなります。

ただし、コンパイラが実際の型を特定できる場合には、最適化によって仮想関数呼び出しが通常の関数呼び出しに変換され、インライン化されることもあります。

このような最適化は、脱仮想化と呼ばれることがあります。

設計が複雑になりやすい

仮想関数や継承を多用すると、クラス階層が深くなり、コードの見通しが悪くなることがあります。

特に、「共通化したい」という理由だけで安易に継承を使うと、後から変更しにくい設計になることがあります。

仮想関数は強力な仕組みですが、使いすぎると保守性を下げる場合もあります。

仮想関数を使うべき場面

共通インターフェースで複数の型を扱いたい場合

複数のクラスに共通の操作があり、それらを同じように扱いたい場合、仮想関数は有効です。

たとえば、複数の図形に対して「描画する」、複数のコマンドに対して「実行する」、複数のデータ形式に対して「読み込む」といった操作を共通化したい場合です。

このような場面では、基底クラスで共通の操作を定義し、派生クラスで具体的な処理を実装すると、柔軟な設計になります。

実行時に処理を切り替えたい場合

プログラムの実行中に、条件によって使うオブジェクトや処理が変わる場合にも、仮想関数は向いています。

たとえば、ユーザーの設定によって使用するアルゴリズムを切り替える場合や、外部から読み込んだデータに応じて処理クラスを変える場合です。

実行時に具体的な型が決まる設計では、仮想関数を使うことで自然に処理を分岐できます。

拡張可能な設計にしたい場合

後から新しい種類のクラスを追加する可能性がある場合、仮想関数を使うと拡張しやすくなります。

既存の呼び出し側のコードを変更せずに、新しい派生クラスを追加できる設計にしやすいためです。

プラグイン、ゲームオブジェクト、GUI部品、ファイルフォーマット処理などでは、この考え方がよく使われます。

仮想関数を使わない方がよい場面

具体的な型が最初から分かっている場合

具体的な型がコンパイル時に分かっていて、実行時に処理を切り替える必要がない場合は、仮想関数を使う必要はありません。

このような場合は、通常のメンバ関数で十分です。

仮想関数を使うと、必要のない実行時コストや設計上の複雑さが増える可能性があります。

単にコードを共通化したいだけの場合

コードの重複を避けたいだけなら、必ずしも継承や仮想関数を使う必要はありません。

関数、テンプレート、コンポジションなどを使った方が、シンプルで保守しやすい場合があります。

特に、継承関係が自然でない場合に無理に基底クラスを作ると、後から設計が窮屈になることがあります。

パフォーマンスが非常に重要な場合

リアルタイム処理、ゲームエンジン、組み込みシステム、大量データ処理などでは、仮想関数の呼び出しコストやメモリコストが問題になる場合があります。

そのような場面では、テンプレート、関数オブジェクト、CRTP、std::variant など、別の方法を検討することがあります。

仮想関数とオブジェクトスライシング

値渡しでは派生クラス部分が失われる

仮想関数を使うときに注意すべき代表的な問題が、オブジェクトスライシングです。

オブジェクトスライシングとは、派生クラスのオブジェクトを基底クラス型の値としてコピーしたときに、派生クラス独自の部分が切り落とされる現象です。

この状態になると、実際には派生クラスのオブジェクトを渡したつもりでも、基底クラス部分だけが残るため、仮想関数による動的な振る舞いが期待通りに働きません。

ポリモーフィズムではポインタや参照を使う

ポリモーフィズムを使いたい場合は、値渡しではなく、ポインタや参照を使うのが基本です。

所有権を持たない場合は参照や生ポインタを使い、動的に生成したオブジェクトを管理する場合は std::unique_ptr などのスマートポインタを使います。

ただし、スマートポインタはポリモーフィズムのためだけに使うものではありません。

主な目的は、動的に確保したオブジェクトの所有権を安全に管理することです。

仮想関数と const

const の有無もオーバーライドに影響する

基底クラスの仮想関数が const メンバ関数である場合、派生クラス側でも同じように const を付ける必要があります。

const の有無が違うと、同じ関数をオーバーライドしているとは扱われません。

このミスは初心者だけでなく、実務でも起こりやすいポイントです。

override を付けていれば、こうした違いをコンパイラが検出してくれるため、安全です。

オーバーライド条件には複数の要素が関係する

オーバーライドには、関数名や引数だけでなく、const の有無、参照修飾子、例外仕様なども関係します。

また、戻り値型は原則として一致している必要がありますが、ポインタや参照を返す場合には、より派生した型を返せる「共変戻り値型」が認められる場合があります。

初心者向けには、まず「関数名、引数、const の有無を一致させる」と理解しておくとよいです。

仮想関数とデフォルト引数

デフォルト引数は静的型で決まる

仮想関数とデフォルト引数を組み合わせる場合は注意が必要です。

仮想関数の本体は、実行時の実際のオブジェクトの型に基づいて決まります。

一方、デフォルト引数は、呼び出し側から見えている静的な型に基づいて決まります。

そのため、基底クラス型のポインタから派生クラスの仮想関数が呼ばれる場合でも、デフォルト引数は基底クラス側のものが使われることがあります。

混乱を避けるため使わない方が無難

この挙動は直感に反することがあるため、仮想関数ではデフォルト引数を使わない方が無難です。

どうしても使う場合は、呼び出し側の静的型によってデフォルト引数が決まることを理解したうえで使う必要があります。

実務では、仮想関数にデフォルト引数を持たせるより、明示的に引数を渡す設計の方が分かりやすくなります。

コンストラクタ・デストラクタ内での仮想関数呼び出し

コンストラクタ内では派生クラス側の関数は呼ばれない

コンストラクタの中で仮想関数を呼び出す場合は注意が必要です。

派生クラスのオブジェクトを生成している途中でも、基底クラスのコンストラクタが実行されている間は、まだ派生クラス部分の構築が完了していません。

そのため、基底クラスのコンストラクタ内で仮想関数を呼び出しても、派生クラス側のオーバーライド関数は呼ばれません。

基底クラス側の関数が呼ばれます。

デストラクタ内でも同じように注意が必要

デストラクタでも同様です。

派生クラスのデストラクタが実行された後、基底クラスのデストラクタが実行される段階では、派生クラス部分はすでに破棄されています。

そのため、基底クラスのデストラクタ内で仮想関数を呼び出しても、派生クラス側の関数にはディスパッチされません。

コンストラクタやデストラクタ内で、仮想関数による動的な振る舞いに依存する設計は避けた方が安全です。

仮想関数とアクセス指定子

仮想関数は public 以外にもできる

仮想関数は、publicprotectedprivate のいずれにもできます。

外部から呼び出す共通インターフェースとして使う場合は、public にするのが一般的です。

派生クラス内部でのみ使わせたい場合は、protected にすることもあります。

派生クラス側でアクセス範囲を変えることもできる

基底クラスで public な仮想関数を、派生クラス側で private としてオーバーライドすることも文法上は可能です。

この場合でも、基底クラス型のポインタや参照を通じて呼び出すと、アクセスチェックは基底クラス側の型に基づいて行われるため、実行時には派生クラス側の関数が呼ばれます。

ただし、派生クラスのオブジェクトから直接その関数を呼ぼうとすると、派生クラス側では private なので呼び出せません。

このような設計は混乱を招きやすいため、意図が明確でない限り避けた方がよいです。

仮想関数とオーバーロードの注意点

派生クラスの同名関数が基底クラスの関数を隠すことがある

派生クラスで基底クラスと同じ名前の関数を定義すると、基底クラス側の同名関数が名前探索で隠れることがあります。

これは、オーバーライドとは別の話です。

引数が異なる複数の関数を基底クラスに用意している場合、派生クラスでその一部だけをオーバーライドすると、他の同名関数が見えにくくなることがあります。

必要に応じて基底クラス側の関数を明示的に使えるようにする

この問題を避けるためには、基底クラス側の同名関数を派生クラス側でも見えるようにする指定を行うことがあります。

これにより、派生クラスで一部の関数をオーバーライドしつつ、基底クラスにある別のオーバーロードも利用できます。

この点は、仮想関数とオーバーロードを組み合わせるときに注意すべきポイントです。

仮想関数と static

static メンバ関数は仮想関数にできない

C++では、static メンバ関数を仮想関数にすることはできません。

仮想関数は、実際のオブジェクトの型に応じて呼び出し先を決める仕組みです。

一方、static メンバ関数は特定のオブジェクトに属さない関数です。

そのため、オブジェクトの実体に基づいて呼び出し先を切り替える仮想関数の仕組みとは相性が合わず、staticvirtual は併用できません。

仮想関数とテンプレート

メンバ関数テンプレートは仮想関数にできない

C++では、メンバ関数テンプレートを仮想関数にすることはできません。

テンプレートは基本的にコンパイル時に型に応じて展開される仕組みです。

一方、仮想関数は実行時に呼び出し先を決める仕組みです。

この性質の違いにより、メンバ関数テンプレートそのものを仮想関数にすることはできません。

クラステンプレートの中に仮想関数を持つことはできる

ただし、クラステンプレートの中に通常の仮想関数を持たせることは可能です。

つまり、「テンプレート」と「仮想関数」がまったく併用できないわけではありません。

できないのは、メンバ関数テンプレートそのものを仮想関数にすることです。

final とは

これ以上オーバーライドさせないための指定

final は、仮想関数をこれ以上派生クラスでオーバーライドさせたくない場合に使います。

派生クラスである仮想関数に final を付けると、そのさらに派生したクラスでは、その関数をオーバーライドできなくなります。

これにより、設計上「この関数の振る舞いはここで固定する」という意図を明確にできます。

クラス自体を継承禁止にすることもできる

final は関数だけでなく、クラス自体にも付けることができます。

クラスに final を付けると、そのクラスはこれ以上継承できません。

これにより、クラス階層を意図しない形で拡張されることを防げます。

また、場合によっては、コンパイラが最適化しやすくなることもあります。

仮想関数とスマートポインタ

動的に生成した派生クラスを安全に管理できる

現代のC++では、動的に生成したオブジェクトを直接管理するより、スマートポインタを使うことが多いです。

たとえば、基底クラス型のスマートポインタで、実際には派生クラスのオブジェクトを管理する設計がよく使われます。

この場合、仮想関数を使えば、基底クラス型として扱いながら、実際の派生クラスに応じた処理を呼び出せます。

スマートポインタ使用時も仮想デストラクタが重要

基底クラス型のスマートポインタが派生クラスのオブジェクトを所有している場合、破棄時には基底クラス型を通じてオブジェクトが削除されます。

そのため、基底クラスのデストラクタは仮想である必要があります。

スマートポインタを使えばメモリ管理は安全になりますが、基底クラスのデストラクタが仮想でなくてもよい、という意味ではありません。

ポリモーフィズムと所有権管理を組み合わせる場合は、この点に注意が必要です。

実務での書き方のポイント

派生クラス側では override を付ける

派生クラスで仮想関数をオーバーライドする場合は、override を付けるのが基本です。

これにより、関数名や引数、const の有無などのミスをコンパイラが検出できます。

override は読み手に対しても、「この関数は基底クラスの関数を上書きしている」という意図を明確に伝えます。

基底クラスには仮想デストラクタを用意する

ポリモーフィズム用の基底クラスでは、仮想デストラクタを用意するのが基本です。

これにより、基底クラス型のポインタやスマートポインタを通じて削除しても、派生クラスの破棄処理が正しく実行されます。

特に、抽象クラスをインターフェースとして使う場合は、仮想デストラクタを忘れないようにするべきです。

using namespace std は実務では避ける

学習用の短いサンプルでは、簡潔に書くために using namespace std; が使われることがあります。

しかし、実務コード、特にヘッダファイルでは避けるべきです。

名前空間をまとめて取り込むと、名前衝突の原因になることがあります。

実務では、標準ライブラリの名前には std:: を明示する方が安全です。

endl は必要な場面で使う

出力時に endl を使うと、改行だけでなく出力バッファのフラッシュも行われます。

単に改行したいだけなら、通常は改行文字で十分です。

endl は間違いではありませんが、頻繁に使うと不要なフラッシュによって効率が悪くなる場合があります。

仮想関数を理解するうえで重要な考え方

仮想関数は「実際の型」で処理を切り替える仕組み

仮想関数の本質は、基底クラス型として扱っていても、実際のオブジェクトの型に応じて処理を切り替えられることです。

この仕組みにより、呼び出し側は具体的な派生クラスを知らなくても、共通のインターフェースを通じて適切な処理を実行できます。

継承とセットで使われるが、乱用は避ける

仮想関数は継承と組み合わせて使う強力な機能です。

しかし、すべての共通化を継承で解決しようとすると、クラス階層が複雑になり、保守しにくい設計になることがあります。

実行時に振る舞いを切り替える必要がある場合には仮想関数が有効ですが、単なるコード共有であれば、他の方法の方が適していることもあります。

ポリモーフィズムには参照やポインタが必要

仮想関数によるポリモーフィズムを正しく使うには、基底クラス型の参照やポインタを通じてオブジェクトを扱う必要があります。

値渡しをすると、オブジェクトスライシングによって派生クラス部分が失われることがあります。

そのため、ポリモーフィズムを使う設計では、値としてコピーするのではなく、参照、ポインタ、スマートポインタを適切に使うことが重要です。

まとめ

C++の仮想関数は、基底クラスのポインタや参照を通じて、実際のオブジェクトの型に応じた関数を呼び出すための仕組みです。

通常の関数では、呼び出す関数がコンパイル時に決まります。

一方、仮想関数では、実行時に実際のオブジェクトの型をもとに呼び出し先が決まります。

これにより、共通のインターフェースで複数の派生クラスを扱うポリモーフィズムを実現できます。

仮想関数を使うと、柔軟で拡張しやすい設計にできます。

呼び出し側は具体的なクラスを知らなくても、共通の操作だけを通じて処理できます。

一方で、仮想関数には実行時コストやメモリコストがあり、設計が複雑になりやすいという注意点もあります。

特に重要なのは、派生クラスでは override を使ってオーバーライドミスを防ぐこと、ポリモーフィズム用の基底クラスでは仮想デストラクタを用意すること、値渡しによるオブジェクトスライシングを避けることです。

仮想関数はC++のオブジェクト指向プログラミングにおいて非常に重要な機能です。

共通の型で複数の具体的な処理を扱いたい場合や、実行時に振る舞いを切り替えたい場合に、正しく使うことで保守性と拡張性の高い設計を実現できます。

以上、C++の仮想関数についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次