こんにちは、ナナです。
オブジェクトの構築を行うための「コンストラクタ」に対して、解体を行うのが「デストラクタ」です。
この「デストラクタ」を使うことによって、オブジェクトを正しく解体することができるようになります。
デストラクタの役割と、なぜこのような機構が必要になったのかを解説していきましょう。
オブジェクトが解体されるという意味
師匠!修行で使っていた手裏剣が錆びてしまって使えなくなりました。この教室の端っこに置いておいてもいいですかね?
なんなら捨てておいてもらってもいいのですが。
ちょっとちょっと、ここはゴミ捨て場じゃないんだよ。自分の荷物は自分でちゃんと始末しておいてよ!
オブジェクト指向におけるオブジェクトとは「ロボット」のようなもの、というのが本サイトの解説イメージでした。
プログラミングの世界も現実世界と同じで必要になったら「ロボット」を作り、いらなくなったら自分で正しく解体しなければなりません。
変数(オブジェクト)が解体されるとは
「変数」は変数定義を行うことでメモリ上に構築され、使い終わると消滅します。
次のプログラムは、スタックメモリ上にローカル変数として定義した変数です。ローカル変数の特徴は、変数定義がされたときに構築され、returnするときに解体される変数であることです。
これはshort型のような組み込み型でも、クラスオブジェクトの変数でも同じなのです。
変数が確保されるメモリの種類は次の3つですが、それぞれ構築・解体のタイミングは異なります。
確保メモリ | 構築タイミング | 解体タイミング |
---|---|---|
スタックメモリ | 関数内での変数定義時 | 関数からreturnする時。 変数定義のブロックを抜けた時。 |
静的メモリ | システム起動時 | システム終了時。 |
動的メモリ | 動的メモリを確保した時 | 動的メモリを解放した時。 |
クラスにおいては変数(オブジェクト)が解体されるタイミングで「デストラクタ」というメンバ関数を呼び出させることができるのです。
デストラクタ:オブジェクトの解体屋
オブジェクトを作るのが「コンストラクタ」で、壊すのが「デストラクタ」ということですね。
「デストラクタ」はプログラムではどのように定義すればよいのですか?
「コンストラクタ」と「デストラクタ」は対の関係性にあたるため、定義方法も非常に似ています。紹介しましょう。
デストラクタの定義ルール
コンストラクタと同様に「デストラクタ」にも定義時のルールがあります。
このルールを守った具体的なデストラクタの定義をプログラムで示しましょう。
class POS
{
public:
int x;
int y;
~POS(); // デストラクタのプロトタイプ宣言
};
// デストラクタのメンバ関数定義
POS::~POS()
{
}
「~」はチルダという記号です。あまり使うことが少ないのでキーボードのどこにあるか迷うかもしれませんが、次の場所に配置されています。
これでクラスオブジェクトが不要になったタイミングで、自動的にデストラクタが呼ばれるようになります。
デストラクタが必要な代表的なシーン
ちょっとちょっと、師匠!さっきの「デストラクタ」って定義しましたけど中身が空っぽじゃないですかっ!
中身をちゃんと書いてください、手抜き禁止ですよ!
うーん、手を抜いているわけじゃないんだよ。やるべき処理がないから空っぽなんだよね。
デストラクタで処理を書くべきシーンは結構限られているんです。
オブジェクトの解体とは占拠したメモリを手放すということ
short型の変数でもクラスオブジェクトの変数でも「解体される」とは、変数が占拠するメモリを手放すことです。
class POS
{
public:
int x;
int y;
};
int main()
{
POS pos;
return 0; // posオブジェクトが解体される
}
POSクラスはメンバ変数「x」「y」を持つだけのクラスですから、デストラクタで特別なことをしなくても、オブジェクトが配置されたメモリ領域は手放すことになります。
このようなクラスにおいては、特にデストラクタを定義せずとも問題は起きないということになります。
では、どのようなシーンなら「デストラクタ」が必要となるかを示しましょう。
オブジェクトが資源を管理している時の解体問題
プログラムにおける代表的な資源といえば、「動的メモリ」やファイルハンドルといった「ハンドル」があります。
これらの資源の特徴は、確保と解放を明示的に行う必要があるということです。
資源 | 資源の管理方法 |
---|---|
ハンドル | ファイルハンドルであればfopen関数/fclose関数で作成・破棄を行う |
動的メモリ | malloc関数/free関数で確保・解放する。C++では後述するnew/freeにて行う |
このような資源をオブジェクト内で管理している場合は扱いに注意が必要です。
次のプログラムは、コンストラクタ内で動的メモリを確保しているHumanクラスです。
#define _CRT_SECURE_NO_WARNINGS
#include <string.h>
#include <stdlib.h>
class Human
{
public:
char * mName; // 氏名
int mAge; // 年齢
// コンストラクタ
Human(char* name, int age);
};
Human::Human(char * name, int age)
{
// 動的メモリ上に氏名を管理
mName = (char *)malloc(strlen(name) + 1);
strcpy(mName, name); // 氏名の設定
mAge = age; // 年齢の設定
}
int main()
{
char name[] = "Taro Yamada";
Human human1(name, 20);
return 0; // human1オブジェクト解体によって氏名がメモリリーク発生
}
Humanクラスが管理する動的メモリは、明示的な解放処理がないとメモリリークが発生します。
このような資源をオブジェクト内に保持している場合が、デストラクタの出番となります。
クラスのメンバ変数に「ハンドル」や「ポインタ」を持つ場合は、デストラクタが必要かどうかを判断する必要があります。
適切に処理しないと解放漏れの問題が起きるため注意が必要です。
デストラクタで資源を解放するプログラム
それでは、この問題をデストラクタを使って解決してみましょう。
#define _CRT_SECURE_NO_WARNINGS
#include <string.h>
#include <stdlib.h>
class Human
{
public:
char * mName; // 氏名
int mAge; // 年齢
// コンストラクタ
Human(char* name, int age);
~Human();
};
Human::Human(char * name, int age)
{
// 動的メモリ上に氏名を管理
mName = (char *)malloc(strlen(name) + 1);
strcpy(mName, name); // 氏名の設定
mAge = age; // 年齢の設定
}
Human::~Human()
{
free(mName);
}
int main()
{
char name[] = "Taro Yamada";
Human human1(name, 20);
return 0; // デストラクタによって動的メモリは解放される
}
デストラクタのメンバ関数内で、動的メモリをfree関数で解放しました。これによって無事にオブジェクトに関連するメモリが全て解放されることになります。
メモリリークはじわじわとシステムを侵食する不具合のため、見つけづらい性質があります。忘れていると思わぬ被害が出るため注意が必要です。
Q&A:デストラクタに関するよくある質問
Q:「デストラクタ」も自分で定義しなければ自動で作られるの?
師匠!「コンストラクタ」はクラスに自分で定義しなければ、自動で「デフォルトコンストラクタ」が作られるって言ってましたよね。
「デストラクタ」はどうなんですか?こいつは定義しないとどうなっちゃうんですか?
はい。デストラクタも自分で定義しない場合は、自動で生成されるメンバ関数となります。
といっても、コンストラクタと同じで何かをしてくれるわけではないので、必要であれば自分で定義することですよ。
次のように、メンバ関数を定義していなくても、「コンストラクタ」と同様に「デストラクタ」も自動で生成されることになります。
class POS
{
public:
int x;
int y;
};