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

C++の仮想コンストラクタを徹底解説!コンストラクタをvirtualにできない理由と仮想デストラクタの重要性

C++における仮想関数の仕組み(仮想機構)は、基底クラスのポインタが派生クラスのオブジェクトを指している場合にのみ正しく機能します。

そしてC++では、コンストラクタを仮想関数(virtual)にすることはできません。その理由は、クラスのコンストラクタが実行される時点では、メモリ上にまだ仮想関数テーブル(vtable)が存在せず、仮想ポインタ(vptr)も定義されていないためです。仮想機構そのものが動作する前提となる情報がまだ用意されていない段階でコンストラクタは呼ばれるため、コンストラクタは常に非仮想(non-virtual)である必要があります。

一方で、デストラクタを仮想関数にすること(仮想デストラクタ)は可能であり、ポリモーフィズムを利用するクラス設計では非常に重要です。

サンプルコード

以下の例では、基底クラスbに仮想デストラクタを定義し、基底クラスのポインタ経由で派生クラスのオブジェクトを削除しています。

#include<iostream>
using namespace std;
class b {
    public:
        b() {
            cout<<"Constructing base \n";
        }
        virtual ~b() {
            cout<<"Destructing base \n";
        }
};
class d : public b {
    public:
        d() {
            cout<<"Constructing derived \n";
        }
        ~d() {
            cout<<"Destructing derived \n";
        }
};
int main(void) {
    d *derived = new d();
    b *bptr = derived;
    delete bptr;
    return 0;
}

実行結果

Constructing base
Constructing derived
Destructing derived
Destructing base

実行結果の解説

コンストラクタは、基底クラス→派生クラスの順に呼び出されます。これは通常のオブジェクト生成と同じ流れです。

注目すべきは削除時の動作です。delete bptr;のように基底クラスのポインタ経由でオブジェクトを削除しても、デストラクタは「派生クラス→基底クラス」の順で正しく呼び出されています。これは基底クラスbのデストラクタにvirtualが付いているためです。

もしデストラクタが仮想関数でなかった場合、このコードでは派生クラスdのデストラクタが呼び出されず、リソースの解放漏れ(メモリリーク)や未定義動作を引き起こす可能性があります。基底クラスのポインタ経由で派生クラスのオブジェクトを扱う設計では、必ずデストラクタをvirtualに宣言するようにしましょう。

  1. 【C++入門】デフォルトコンストラクタの基礎と使い方をわかりやすく解説

    C++のコンストラクタとは コンストラクタ(constructor)とは、クラスの新しいオブジェクト(インスタンス)を生成した瞬間に自動的に実行される特殊なメンバ関数です。オブジェクトの初期化処理を記述するための仕組みで、C++プログラミングにおいて非常に重要な役割を担っています。 コンストラクタの基本ルール クラス名と完全に同じ名前を持つ 戻り値の型を持たない(voidすら指定できない) オブジェクト生成時に自動的に呼び出される 特に引数を取らないコンストラクタは「デフォルトコンストラクタ」と呼ばれる メンバ変数に初期値を設定したい場合など、コンストラクタはとても便利です。以下のサンプル

  2. C++のコピーコンストラクタとは?仕組みと実装方法をサンプルコード付きで解説

    コピーコンストラクタは、コンストラクタの一種です。同じクラスのオブジェクトを受け取り、その値を使って新しいオブジェクトを生成・初期化します。もしクラス内でコピーコンストラクタが定義されていない場合、コンパイラが自動的にデフォルトのコピーコンストラクタを生成します。ただし、ポインタ変数や動的メモリ確保(new/delete)を持つクラスでは、デフォルトのコピーは浅いコピー(shallow copy)となり、同じメモリ領域を複数のオブジェクトが参照してしまう問題が発生します。そのため、このようなクラスでは独自のコピーコンストラクタを定義することが必須となります。コピーコンストラクタのサンプルプログ