はじめに
C++では、通常の変数は宣言されたスコープを抜けると自動的に破棄されます。しかし、プログラムの実行時まで必要なサイズが分からない配列や、関数のスコープを超えて存在し続けるオブジェクトを扱いたい場合、ヒープと呼ばれる特別なメモリ領域に、動的にメモリを確保する必要があります。
この動的なメモリ確保と解放を担うのが、newとdeleteという対の演算子です。この記事では、newとdeleteの基本的な使い方から、配列版、例外を送出しないnothrow版、そして特殊な「配置new」までを網羅的に解説します。
1. 単一オブジェクトの確保と解放
newで単一のオブジェクトを、deleteでそれを解放します。
サンプルコード
#include <iostream>
int main() {
// 1. newで、int型1つ分のメモリを確保し、そのアドレスをポインタに格納
int* p_int = new int;
*p_int = 100; // 確保したメモリに値を書き込む
std::cout << "確保したメモリの値: " << *p_int << std::endl;
// 2. deleteで、確保したメモリを解放
delete p_int;
return 0;
}
【重要】: newで確保したメモリは、必ずdeleteで解放しなければなりません。解放を忘れると、そのメモリはプログラム終了まで誰にも使われない状態になり、「メモリリーク」の原因となります。
2. 配列の確保と解放 (new[] / delete[])
動的に配列を確保するにはnew[]を、解放するにはdelete[]を使います。
サンプルコード
#include <iostream>
int main() {
// 1. new[]で、int型3つ分の配列メモリを確保
int* p_array = new int[3];
p_array[0] = 10;
p_array[1] = 20;
p_array[2] = 30;
// 2. delete[]で、確保した「配列」を解放
delete[] p_array;
return 0;
}
【重要】: new[]で確保したメモリは、必ずdelete[]で解放します。delete([]なし)を使ってしまうと、最初の要素しか正しく解放されない可能性があり、未定義の動作を引き起こします。
3. 例外を送出しないnothrow
通常、newはメモリ確保に失敗するとstd::bad_allocという例外をスローします。例外処理を使いたくない場合、new(nothrow)と書くことで、失敗時に例外の代わりにnullptrを返すようになります。
サンプルコード
#include <iostream>
int main() {
// new(nothrow)で、メモリ確保を試みる
// 非常に大きなサイズを指定して、失敗をシミュレート
int* p_huge = new(std::nothrow) int[999999999];
if (p_huge == nullptr) {
std::cout << "メモリ確保に失敗しました。" << std::endl;
} else {
std::cout << "メモリ確保に成功しました。" << std::endl;
delete[] p_huge;
}
return 0;
}
4. 配置new (Placement New)
配置newは、既に確保済みの特定のメモリ領域上に、オブジェクトを構築(コンストラクタを呼び出す)するための、非常に特殊なnewです。 通常のnewが「メモリ確保」と「オブジェクト構築」の両方を行うのに対し、配置newは「オブジェクト構築」だけを行います。
サンプルコード
#include <iostream>
#include <cstdlib> // malloc, free
struct Widget {
Widget() { std::cout << "Widgetのコンストラクタ" << std::endl; }
~Widget() { std::cout << "Widgetのデストラクタ" << std::endl; }
};
int main() {
// 1. mallocで、単なる生のメモリ領域を確保
void* raw_memory = malloc(sizeof(Widget));
// 2. 配置newで、そのメモリ領域上にWidgetオブジェクトを構築
Widget* p_widget = new(raw_memory) Widget;
// 3. デストラクタを明示的に呼び出す
p_widget->~Widget();
// 4. mallocで確保したメモリをfreeで解放
free(raw_memory);
return 0;
}
解説: 配置newで構築したオブジェクトのメモリは、deleteでは解放できません。デストラクタを明示的に呼び出した後、元のメモリ確保手段(この例ではfree)で解放する必要があります。これは、メモリプールなど、高度なメモリ管理を行うライブラリ開発などで使われる上級者向けの機能です。
まとめ
今回は、C++の動的なメモリ確保と解放について解説しました。
new/delete: 単一オブジェクトnew[]/delete[]: 配列new(nothrow): 例外の代わりにnullptrを返す- 配置
new: 既存のメモリ領域にオブジェクトを構築する特殊な形式
現代のC++では、newとdeleteを手動で管理するのはメモリリークの温床となるため、std::vector, std::string, std::unique_ptr, std::shared_ptr といった、RAIIに基づいたコンテナやスマートポインタを使うことが強く推奨されます。
