virtual function。

仮想関数は、派生クラスで振る舞いをオーバーライドできるメンバ関数である。

非仮想関数と異なり、仮想関数では、コンパイル時に実際のオブジェクト(C++)の型が分からなくても、オーバーライドした振る舞いが保たれる。
つまり、派生クラスのオブジェクトを基底クラスへの左辺値参照(C++)ポインタ(C++)を通して扱っている場合でも、オーバーライドされた仮想関数を呼び出すと、派生クラス側で定義された関数が呼ばれる。

class Base
{
public:
    virtual std::string_view getName() const
    {
        return "Base";
    }
};

派生クラスが同じシグネチャの関数を定義すると、その関数が基底クラス版をオーバーライドする。
オーバーライドとみなされるには、関数名、引数型、const修飾の有無などが対応している必要がある。

virtualの有無による違い

非virtual:

class Base
{
public:
    std::string_view getName() const
    {
        return "Base";
    }
};
 
class Derived : public Base
{
public:
    std::string_view getName() const
    {
        return "Derived";
    }
};
 
Derived d{};
Base& ref{ d };

この場合、ref.getName()Base::getName()として解決される。

virtual:

class Base
{
public:
    virtual std::string_view getName() const
    {
        return "Base";
    }
};
 
class Derived : public Base
{
public:
    std::string_view getName() const
    {
        return "Derived";
    }
};
 
Derived d{};
Base& ref{ d };

この場合、ref.getName()Derived::getName()を呼び出す。

ただし、ref.Base::getName()のようにスコープ解決演算子で基底クラス版を明示して呼ぶと、仮想呼び出しは行われず、Base::getName()が呼ばれる。

用途

仮想関数を使うと、複数の派生クラスを共通の基底クラスとして扱いながら、それぞれに応じた振る舞いをさせられる。

class Animal
{
public:
    virtual std::string_view speak() const
    {
        return "???";
    }
};
 
class Cat : public Animal
{
public:
    std::string_view speak() const
    {
        return "Meow";
    }
};
 
class Dog : public Animal
{
public:
    std::string_view speak() const
    {
        return "Woof";
    }
};

Animal&Animal*を通してCatDogを扱っても、speak()は実際の型に応じて呼び分けられる。

純粋仮想関数

派生クラスでのオーバーライドを必須にする仮想関数を、純粋仮想関数 (pure virtual function) という。
純粋仮想関数にするには、宣言の末尾に= 0を書く。

class Polygon
{
public:
    virtual int area() const = 0;
};

通常の仮想関数は基底クラス側で既定の実装を持てるが、純粋仮想関数は「この操作は存在するが、基底クラスでは実装しないので派生クラスで定義してほしい」という意味を表す。

抽象基底クラス

純粋仮想関数を1つ以上持つクラスは、抽象基底クラス (abstract base class) になる。
抽象基底クラスは、そのままではインスタンス化できない。

class Polygon
{
public:
    virtual int area() const = 0;
};

このようなクラスは、共通の操作や共通部分を基底クラス側で定義しつつ、具体的な振る舞いは派生クラスに任せたいときに使われる。

インターフェースクラス

状態や実装をほとんど持たず、純粋仮想関数によって操作だけを定義するクラスは、インターフェースクラス (interface class) として使われる。

class Drawable
{
public:
    virtual void draw() const = 0;
};

このようなクラスは、「何ができるか」を表し、実際にどう行うかは派生クラスが実装する。
抽象基底クラスの中でも、特にインターフェース定義に特化したものと考えられる。

参考