C++では、オブジェクトの生成時にコンストラクタが呼ばれ、破棄されるときにデストラクタが自動的に呼ばれます。この仕組みは、オブジェクトの「生まれてから消えるまで」の流れを制御するために重要です。
C++では new を使うとヒープ領域にメモリを確保し、delete で解放します。この2つを使うことで、プログラム実行中に「必要なタイミングでオブジェクトを作り、不要になったら消す」ことができます。
// 17_memory_basic.cpp
#include <iostream>
using namespace std;
class Player {
public:
Player() { cout << "プレイヤー生成!" << endl; }
~Player() { cout << "プレイヤー削除!" << endl; }
};
int main() {
Player* p = new Player(); // 動的に生成
cout << "プレイ中..." << endl;
delete p; // 明示的に削除(デストラクタが呼ばれる)
return 0;
}
デストラクタは ~Player() のように、~クラス名 と同名関数として定義します。delete が動作するタイミングで自動的に呼び出されるメソッドとなります。
実行結果:
プレイヤー生成! プレイ中... プレイヤー削除!
new で確保したオブジェクトは delete で明示的に破棄しないと、メモリが残り続けます(メモリリーク)。
Player* p = new Player(); // コンストラクタが呼ばれる
class Enemy { public: Enemy(int hp) {/*...*/} };
Enemy* e = new Enemy(100); // 引数を渡して生成
Enemy* es = new Enemy[10]; // デフォルトコンストラクタ必須
充分なメモリがないと std::bad_alloc 例外が投げられます。例外を使わない環境では new (nothrow) で nullptr が返ることもあります。
Enemy* e = new (nothrow) Enemy(100);
if (!e) { /* メモリ不足時の処理 */ }
delete p; // デストラクタが呼ばれ、メモリが解放される p = nullptr; // ぶら下がりポインタ対策(推奨)
delete[] es; // 配列は必ず delete[] es = nullptr;
delete を二回呼ばないこと。delete 後に nullptr を代入しておくと安全。delete nullptr; は安全な空操作です。
ゲームの世界では、次々とキャラクターや弾、エフェクトなどが登場します。これらをすべて最初からメモリ上に確保しておくのは非効率です。
new で新しい敵オブジェクトを生成delete で不要になった敵のメモリを解放// 17_game_enemy_spawn.cpp
#include <iostream>
#include <vector>
using namespace std;
class Enemy {
public:
Enemy(int id) { cout << "敵" << id << " 出現!" << endl; }
~Enemy() { cout << "敵 消滅!" << endl; }
};
int main() {
vector<Enemy*> enemies;
// 敵を3体出現させる
for (int i = 0; i < 3; i++) {
enemies.push_back(new Enemy(i + 1));
}
cout << "敵を全て倒した!" << endl;
// 倒した敵を削除
for (Enemy* e : enemies) {
delete e;
}
return 0;
}
実行結果:
敵1 出現! 敵2 出現! 敵3 出現! 敵を全て倒した! 敵 消滅! 敵 消滅! 敵 消滅!
次のコードの実行結果はどれですか?
#include <iostream>
using namespace std;
class Player {
public:
Player() { cout << "生成" << endl; }
~Player() { cout << "削除" << endl; }
};
int main() {
Player* p = new Player();
cout << "使用中" << endl;
delete p;
return 0;
}
new Player() でコンストラクタが呼ばれ "生成"、その後 "使用中" を表示、delete p でデストラクタが呼ばれ "削除" と表示されます。
デストラクタの定義として正しいものはどれですか?(クラス名が Enemy の場合)
~クラス名() の形で定義します。引数も戻り値も持ちません。オブジェクトが破棄される際に自動的に呼ばれます。
次のコードには問題があります。何が問題ですか?
int main() {
Enemy* e = new Enemy(1);
// delete を忘れた
return 0;
}
new で確保したメモリは delete しない限り解放されません。デストラクタも呼ばれません。プログラムが短時間で終了する場合はOSが回収しますが、長時間動作するプログラムや大量に生成するゲームでは深刻なメモリリークになります。
次のコードの出力はどれですか?
#include <iostream>
#include <vector>
using namespace std;
class Item {
int id;
public:
Item(int i) : id(i) { cout << "Item" << id << " 生成" << endl; }
~Item() { cout << "Item" << id << " 削除" << endl; }
};
int main() {
vector<Item*> items;
items.push_back(new Item(1));
items.push_back(new Item(2));
for (Item* it : items) delete it;
return 0;
}
push_back の順に Item1 → Item2 が生成されます。その後 range-for で先頭から順に delete するので Item1 → Item2 の順でデストラクタが呼ばれます。
// main.cpp
#include <iostream>
#include <vector>
using namespace std;
class Enemy {
public:
Enemy(int id) { cout << "敵" << id << " 出現!" << endl; }
~Enemy() { cout << "敵 消滅!" << endl; }
};
int main() {
vector<Enemy*> enemies;
for (int i = 0; i < 3; i++) {
enemies.push_back(new Enemy(i + 1));
}
cout << "敵を全て倒した!" << endl;
for (Enemy* e : enemies) {
delete e;
}
return 0;
}
17_01_Destructor の main.cpp をコピーして 17_02_DestructorMod に貼り付け、次の変更を加えてみましょう:
Enemy クラスに int hp を追加するmain() で各敵に異なる HP を設定して生成・削除を確認するnew / delete を使うと、実行中に動的にオブジェクトを生成・削除できるdelete 後は nullptr を代入する習慣をつけよう