あなたはこれらの答えを使って仮説に取り掛かっているので、わかりやすくするために、私はより単純な、より現実的な説明にしようとします。
オブジェクト指向設計の基本的な関係は、IS-AとHAS-Aの2つです。私はそれらを作りませんでした。それが彼らの呼び名です。
IS-Aは、特定のオブジェクトが、クラス階層でその上にあるクラスのオブジェクトであることを識別することを示します。果物クラスのサブクラスである場合、バナナオブジェクトは果物オブジェクトです。これは、果物クラスが使用できる場所ならどこでも、バナナが使用できることを意味します。ただし、反射的ではありません。特定のクラスが必要な場合、特定のクラスを基本クラスに置き換えることはできません。
Has-aは、オブジェクトが複合クラスの一部であり、所有権関係があることを示しています。これは、C ++では、それがメンバーオブジェクトであることを意味します。したがって、それ自体を破棄する前に、所有クラスがそのクラスを破棄するか、所有権を引き継ぐ責任があります。
これら2つの概念は、c ++のような多重継承モデルよりも単一継承言語で実現する方が簡単ですが、ルールは基本的に同じです。Fruitクラスポインターを受け取る関数にBananaクラスポインターを渡すなど、クラスIDがあいまいな場合に問題が発生します。
仮想関数は、第一に、ランタイムのものです。これは、実行中のプログラムで呼び出されるときに実行する関数を決定するために使用されるという点で、ポリモーフィズムの一部です。
virtualキーワードは、クラスIDについてあいまいな場合に関数を特定の順序でバインドするコンパイラディレクティブです。仮想関数は常に(私が知る限り)親クラスにあり、メンバー関数とその名前のバインドは最初にサブクラス関数で、次に親クラス関数で行う必要があることをコンパイラーに示します。
Fruitクラスには、デフォルトで「NONE」を返す仮想関数color()を含めることができます。Bananaクラスのcolor()関数は、「YELLOW」または「BROWN」を返します。
しかし、Fruitポインタを取る関数が、それに送信されたバナナクラスでcolor()を呼び出す場合、どのcolor()関数が呼び出されますか?関数は通常、Fruitオブジェクトに対してFruit :: color()を呼び出します。
99%の時間は意図したものではありません。しかし、Fruit :: color()が仮想として宣言されている場合、正しいcolor()関数が呼び出し時にFruitポインターにバインドされるため、オブジェクトに対してBanana:color()が呼び出されます。ランタイムは、Fruitクラス定義で仮想としてマークされているため、ポインターが指すオブジェクトをチェックします。
これは、サブクラスの関数をオーバーライドすることとは異なります。その場合、それが果物へのIS-Aポインターであることを知っているだけであれば、果物ポインターは果物::色()を呼び出します。
そこで、「純粋な仮想関数」というアイデアが浮かび上がります。純粋さはそれとは何の関係もないので、それはかなり残念なフレーズです。これは、基本クラスのメソッドが呼び出されないように意図されていることを意味します。実際、純粋な仮想関数を呼び出すことはできません。ただし、それでも定義する必要があります。関数シグネチャが存在している必要があります。多くのプログラマーは完全を期すために空の実装{}を作成しますが、そうでない場合はコンパイラーが内部で生成します。その場合、ポインターがFruitへのポインターであっても関数が呼び出されると、banana :: color()が呼び出されます。これは、color()の唯一の実装であるためです。
パズルの最後のピース:コンストラクタとデストラクタ。
純粋な仮想コンストラクタは完全に違法です。それだけです。
ただし、純粋な仮想デストラクタは、基本クラスインスタンスの作成を禁止する場合に機能します。基本クラスのデストラクタが純粋仮想の場合、サブクラスのみをインスタンス化できます。慣例では、これを0に割り当てます。
virtual ~Fruit() = 0; // pure virtual
Fruit::~Fruit(){} // destructor implementation
この場合、実装を作成する必要があります。コンパイラは、これがあなたのやっていることを知っており、正しく実行していることを確認します。または、コンパイルする必要のあるすべての関数にリンクできないことを強く訴えます。クラス階層をどのようにモデル化しているかについて正しい方向に進んでいない場合、エラーは混乱を招く可能性があります。
したがって、この場合、Fruitのインスタンスを作成することは禁止されていますが、Bananaのインスタンスを作成することは許可されています。
Bananaのインスタンスを指すFruitポインターの削除を呼び出すと、最初にBanana ::〜Banana()が呼び出され、次にFuit ::〜Fruit()が常に呼び出されます。何があっても、サブクラスデストラクタを呼び出すときは、基本クラスデストラクタが続く必要があるためです。
それは悪いモデルですか?設計段階ではより複雑ですが、実行時に正しいリンクが確実に実行され、アクセスされているサブクラスが正確に曖昧な場合にサブクラス関数が実行されます。
ジェネリックポインターもあいまいなポインターもない正確なクラスポインターのみを渡すようにC ++を作成する場合、仮想関数は実際には必要ありません。しかし、型の実行時の柔軟性が必要な場合(Apple Banana Orange ==> Fruitなど)、関数は冗長コードが少なくなり、より簡単で多用途になります。果物の種類ごとに関数を記述する必要がなくなり、すべての果物が独自の正しい関数でcolor()に応答することがわかります。
この長々とした説明が、混乱を招くのではなく、概念を固めることを願っています。よく見る良い例がたくさんあり、十分に見て実際にそれらを実行し、それらをいじると、あなたはそれを得るでしょう。