はじめに
C++で new
を使って動的にメモリを確保した場合、必ず delete
を使って手動でメモリを解放しなければならず、もし delete
を忘れるとメモリリークの原因になります。
この問題を解決するのが、C++11で導入されたスマートポインタの一つ、std::unique_ptr
です。unique_ptr
は、RAII(Resource Acquisition Is Initialization)という原則に基づき、オブジェクトのライフタイム管理を自動化します。
unique_ptr
が生成される際に、メモリ(リソース)の所有権を得る。unique_ptr
がスコープを抜けて破棄される際に、そのデストラクタが自動的にdelete
を呼び出し、リソースを解放する。
この記事では、unique_ptr
の3つの主要な使い方を解説します。
- 基本的な使い方(自動的なメモリ解放)
- 所有権の移動 (
std::move
) - カスタムデリータの指定
【前提】C++11とは?
C++11は、2011年に正式化されたC++言語のメジャーアップデート版です。unique_ptr
はこのC++11で導入されたため、利用するにはC++11以降に対応したコンパイラが必要です。
1. 基本的な使い方
unique_ptr
でラップされたポインタは、unique_ptr
変数がスコープを抜けるときに、自動的に delete
されます。
サンプルコード
#include <iostream>
#include <memory> // unique_ptr, make_unique
#include <string>
using namespace std;
struct Resource {
string name;
Resource(string n) : name(n) { cout << "リソース「" << name << "」確保" << endl; }
~Resource() { cout << "リソース「" << name << "」解放" << endl; }
void use() { cout << "リソース「" << name << "」使用中..." << endl; }
};
void basic_usage_example() {
cout << "--- 関数に入りました ---" << endl;
// make_unique (C++14) を使って、安全にunique_ptrを生成
unique_ptr<Resource> p_res = make_unique<Resource>("データA");
// 通常のポインタのように -> でメンバにアクセス
p_res->use();
cout << "--- 関数を抜けます ---" << endl;
// ここで p_res がスコープを抜けるため、デストラクタが呼ばれ、
// Resourceオブジェクトが自動的にdeleteされる
}
int main() {
basic_usage_example();
return 0;
}
2. 所有権の移動 (std::move
)
unique_ptr
は、その名の通り所有権がユニーク(唯一)であるため、コピーすることは固く禁じられています。ある unique_ptr
が持つ所有権を別の unique_ptr
に移したい場合は、std::move
を使って所有権を移動させる必要があります。
サンプルコード
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
struct Resource { /* ... */ };
int main() {
unique_ptr<Resource> p1 = make_unique<Resource>();
// NG: コピーはコンパイルエラーになる!
// unique_ptr<Resource> p2 = p1;
// OK: std::moveで、p1の所有権をp2に「移動」する
unique_ptr<Resource> p2 = move(p1);
// 所有権を失ったp1は、もはや何も指していない (nullptr)
if (!p1) {
cout << "p1は所有権を失いました。" << endl;
}
// vectorに所有権を移動させることも可能
vector<unique_ptr<Resource>> vec;
vec.push_back(move(p2));
return 0;
}
解説: move
を行った後、元のポインタ(p1
やp2
)は空になり、リソースを管理しなくなります。所有権は常にただ一つの unique_ptr
が持ちます。
3. カスタムデリータの指定
unique_ptr
は、delete
以外の方法でリソースを解放する必要がある場合(例: fopen
で開いたファイルを fclose
する)のために、カスタムデリータを指定できます。
サンプルコード
#include <iostream>
#include <memory>
#include <cstdio> // FILE, fopen, fclose, fwrite
using namespace std;
// 1. FILE* を fclose するためのカスタムデリータ関数
void file_closer(FILE* fp) {
if (fp) {
fclose(fp);
cout << "カスタムデリータ: ファイルを閉じました。" << endl;
}
}
int main() {
// 2. unique_ptrのテンプレート第2引数に、デリータの型を指定
unique_ptr<FILE, decltype(&file_closer)> file_ptr(
fopen("test.txt", "w"), // リソースを取得
file_closer // 解放に使う関数を渡す
);
if (file_ptr) {
fwrite("hello", 1, 5, file_ptr.get());
}
// main関数を抜ける際に、file_ptrのデストラクタが
// 自動的に file_closer(fp) を呼び出す
return 0;
}
解説: unique_ptr
のテンプレート引数の2番目にデリータの型 (decltype(&file_closer)
) を、コンストラクタの2番目の引数にデリータ関数そのものを渡すことで、delete
の代わりにその関数がリソース解放時に呼び出されるようになります。
まとめ
今回は、C++11の std::unique_ptr
を使った、安全な動的メモリ管理の基本を解説しました。
unique_ptr
は、リソースの所有権を一つに限定する。- スコープを抜けると、自動的にリソースを解放してくれるため、
delete
の呼び忘れがなくなる。 - 所有権はコピーできず、
std::move
で移動させる。 - カスタムデリータを指定することで、
delete
以外の解放処理にも対応できる。
new
を使う場面では、原則として unique_ptr
(または shared_ptr
)を使うのが、現代C++におけるメモリリークを防ぐための最も基本的で強力な方法です。