RAIIとは
RAII (Resource Acquisition Is Initialization) は、C++のリソース管理の基本原則。
「リソースの取得をオブジェクトの初期化に、解放をデストラクタに任せる」
これを理解すると、C++のリソース管理がグッと楽になる。
// ❌ 手動管理(危険)
FILE* file = fopen("data.txt", "r");
// ... 例外が発生したら? → リソースリーク
fclose(file);
// ✓ RAII(安全)
{
std::ifstream file("data.txt");
// ... 例外が発生しても
} // スコープを抜けると自動でclose
基本的な実装
class FileHandle {
private:
FILE* file;
public:
FileHandle(const char* filename, const char* mode) {
file = fopen(filename, mode);
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandle() {
if (file) {
fclose(file); // 確実に解放
}
}
// コピー禁止
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// ムーブは許可
FileHandle(FileHandle&& other) noexcept : file(other.file) {
other.file = nullptr;
}
};
使用例:
{
FileHandle file("data.txt", "w");
file.write("Hello!");
} // 自動でfclose
ロックガード
std::lock_guardはmutexのRAIIラッパーです。
std::mutex mtx;
void safe_increment() {
std::lock_guard<std::mutex> lock(mtx); // ロック取得
++counter;
} // 自動でunlock
// ❌ 手動unlock(危険)
void dangerous_increment() {
mtx.lock();
++counter;
// ここで例外が発生すると永遠にロック!
mtx.unlock();
}
各種ロックガード
| クラス | 用途 |
|---|---|
lock_guard |
基本的なスコープロック |
unique_lock |
柔軟(遅延ロック、条件変数) |
scoped_lock |
複数mutexを安全にロック |
// 複数mutexを安全にロック(デッドロック回避)
std::scoped_lock lock(mutex1, mutex2);
スマートポインタ
// unique_ptr: 単独所有
{
auto ptr = std::make_unique<MyClass>();
} // 自動でdelete
// shared_ptr: 共有所有
{
auto ptr1 = std::make_shared<MyClass>();
auto ptr2 = ptr1; // 共有
} // 参照カウントが0になったらdelete
スコープガード
任意のクリーンアップ処理をRAIIで実行:
template<typename F>
class ScopeGuard {
private:
F cleanup;
bool active;
public:
explicit ScopeGuard(F f) : cleanup(std::move(f)), active(true) {}
~ScopeGuard() {
if (active) cleanup();
}
void dismiss() { active = false; } // キャンセル
ScopeGuard(const ScopeGuard&) = delete;
};
template<typename F>
ScopeGuard<F> make_scope_guard(F f) {
return ScopeGuard<F>(std::move(f));
}
使用例:
int* raw_ptr = new int(42);
auto guard = make_scope_guard([&]() {
delete raw_ptr;
});
// 例外が発生してもguardが確実にdelete
トランザクション
成功時のみコミット、失敗時はロールバック:
class Transaction {
private:
bool committed = false;
std::vector<std::function<void()>> rollback_actions;
public:
void add_rollback(std::function<void()> action) {
rollback_actions.push_back(std::move(action));
}
void commit() { committed = true; }
~Transaction() {
if (!committed) {
// 逆順でロールバック
for (auto it = rollback_actions.rbegin();
it != rollback_actions.rend(); ++it) {
(*it)();
}
}
}
};
使用例:
Transaction tx;
// 操作1
do_operation1();
tx.add_rollback([]() { undo_operation1(); });
// 操作2
do_operation2();
tx.add_rollback([]() { undo_operation2(); });
tx.commit(); // 成功ならコミット
// 例外でcommit()が呼ばれなければ自動ロールバック
標準ライブラリのRAII
| クラス | 管理するリソース |
|---|---|
std::string |
動的メモリ |
std::vector |
動的配列 |
std::fstream |
ファイル |
std::unique_ptr |
動的オブジェクト |
std::shared_ptr |
共有オブジェクト |
std::lock_guard |
mutex |
std::thread |
スレッド(joinable) |
アンチパターン
❌ rawポインタでnew/delete
// 危険
int* p = new int[100];
// ... 例外が発生したら?
delete[] p;
// 安全
auto p = std::make_unique<int[]>(100);
❌ リソースをクラス外で管理
// 危険:解放忘れの可能性
FILE* open_file(const char* name);
void close_file(FILE* f);
// 安全:RAIIでラップ
class File {
FILE* f;
public:
File(const char* name) : f(fopen(name, "r")) {}
~File() { if (f) fclose(f); }
};
❌ 手動でのunlock
// 危険
mtx.lock();
do_something(); // 例外で永遠にロック
mtx.unlock();
// 安全
std::lock_guard<std::mutex> lock(mtx);
do_something();
RAIIの設計原則
- コンストラクタでリソース取得
- デストラクタでリソース解放
- コピーは禁止 or 深いコピー
- ムーブは許可(所有権移動)
- 例外安全性を確保
class Resource {
public:
Resource() { /* 取得 */ }
~Resource() { /* 解放 */ }
// コピー禁止
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
// ムーブ許可
Resource(Resource&&) noexcept;
Resource& operator=(Resource&&) noexcept;
};
まとめ
| リソース | RAIIラッパー |
|---|---|
| メモリ |
unique_ptr, shared_ptr
|
| ファイル | fstream |
| mutex |
lock_guard, unique_lock
|
| その他 | カスタムクラス or スコープガード |
C++でリソースを扱うなら、必ずRAIIを使いましょう。
// これだけ覚える
{
auto resource = make_resource();
} // 自動で解放