10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

C++の例外安全性には3つのレベルがある。

「例外が飛んでもリソースリークしない」とか「完全にロールバックできる」とか、ちゃんと意識して書いてる?

例外安全性の3つのレベル

レベル 説明
Basic(基本保証) 例外発生時もリソースリークなし、オブジェクトは有効状態
Strong(強い保証) 例外発生時、操作前の状態に完全ロールバック
Nothrow(無例外保証) 例外を投げない

1. 基本保証の例

class BasicSafe {
    int* data;
    size_t size;
    
public:
    BasicSafe(size_t n) : data(new int[n]), size(n) {
        for (size_t i = 0; i < n; ++i) data[i] = 0;
    }
    
    ~BasicSafe() { delete[] data; }
    
    // コピー代入(基本保証)
    BasicSafe& operator=(const BasicSafe& other) {
        if (this != &other) {
            int* new_data = new int[other.size];  // 先に確保
            memcpy(new_data, other.data, other.size * sizeof(int));
            delete[] data;  // 古いデータを解放
            data = new_data;
            size = other.size;
        }
        return *this;
    }
};

ポイント: 新しいリソースを確保してから古いリソースを解放

2. 強い保証(copy-and-swap イディオム)

class StrongSafe {
    int* data;
    size_t size;
    
public:
    StrongSafe(size_t n) : data(new int[n]), size(n) {}
    ~StrongSafe() { delete[] data; }
    
    StrongSafe(const StrongSafe& other) 
        : data(new int[other.size]), size(other.size) {
        memcpy(data, other.data, size * sizeof(int));
    }
    
    // swap は nothrow
    friend void swap(StrongSafe& a, StrongSafe& b) noexcept {
        using std::swap;
        swap(a.data, b.data);
        swap(a.size, b.size);
    }
    
    // 強い保証: copy-and-swap
    StrongSafe& operator=(StrongSafe other) {  // 値渡しでコピー
        swap(*this, other);  // nothrow
        return *this;
    }  // other のデストラクタが古いデータを解放
    
    // 強い保証のpush_back
    void push_back(int value) {
        int* new_data = new int[size + 1];  // 失敗したら例外
        memcpy(new_data, data, size * sizeof(int));
        new_data[size] = value;
        // ここまで成功したら入れ替え
        delete[] data;
        data = new_data;
        ++size;
    }
};

ポイント: 全ての変更を準備してから、nothrowな操作で適用

3. nothrow保証

class NothrowSafe {
    int value;
    
public:
    NothrowSafe(int v = 0) noexcept : value(v) {}
    ~NothrowSafe() noexcept = default;
    
    NothrowSafe(const NothrowSafe&) noexcept = default;
    NothrowSafe& operator=(const NothrowSafe&) noexcept = default;
    NothrowSafe(NothrowSafe&&) noexcept = default;
    NothrowSafe& operator=(NothrowSafe&&) noexcept = default;
    
    int get() const noexcept { return value; }
    void set(int v) noexcept { value = v; }
    
    friend void swap(NothrowSafe& a, NothrowSafe& b) noexcept {
        int tmp = a.value;
        a.value = b.value;
        b.value = tmp;
    }
};

// noexcept チェック
static_assert(is_nothrow_copy_constructible_v<NothrowSafe>);
static_assert(is_nothrow_move_constructible_v<NothrowSafe>);

4. RAII によるリソース管理

class FileHandle {
    FILE* fp;
    
public:
    explicit FileHandle(const char* filename, const char* mode) 
        : fp(fopen(filename, mode)) {
        if (!fp) throw runtime_error("Failed to open file");
    }
    
    ~FileHandle() noexcept {
        if (fp) fclose(fp);
    }
    
    // コピー禁止
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    
    // ムーブ可能
    FileHandle(FileHandle&& other) noexcept : fp(other.fp) {
        other.fp = nullptr;
    }
    
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            if (fp) fclose(fp);
            fp = other.fp;
            other.fp = nullptr;
        }
        return *this;
    }
    
    FILE* get() const noexcept { return fp; }
};

// 使用例 - 例外が発生してもファイルは必ず閉じられる
void safe_file_operation() {
    FileHandle fh("data.txt", "w");
    // 処理...
}  // 自動的にクローズ

5. ScopeGuard パターン

template<typename F>
class ScopeGuard {
    F func;
    bool active;
    
public:
    explicit ScopeGuard(F f) : func(std::move(f)), active(true) {}
    
    ~ScopeGuard() {
        if (active) func();
    }
    
    void dismiss() noexcept { active = false; }
    
    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;
};

template<typename F>
ScopeGuard<F> make_scope_guard(F f) {
    return ScopeGuard<F>(std::move(f));
}

// 使用例
void example() {
    int* ptr = new int(42);
    auto guard = make_scope_guard([&] { delete ptr; });
    
    // 処理...
    // 例外が発生してもptrは必ず解放される
    
    guard.dismiss();  // 正常終了時はクリーンアップ不要
}

6. トランザクション的な操作

class Account {
    int balance;
    string name;
    
public:
    Account(const string& n, int b) : balance(b), name(n) {}
    
    // 強い保証: 成功か完全なロールバック
    static void transfer(Account& from, Account& to, int amount) {
        if (from.balance < amount) {
            throw runtime_error("Insufficient balance");
        }
        
        // 両方の変更を一時変数で計算
        int new_from = from.balance - amount;
        int new_to = to.balance + amount;
        
        // 計算成功後に実際に適用(nothrow)
        from.balance = new_from;
        to.balance = new_to;
    }
};

7. std::vectorの例外安全性

// push_back は強い保証
// reserve はメモリ確保に失敗したら例外
// swap は nothrow

vector<int> v = {1, 2, 3};
v.push_back(4);  // 強い保証

v.reserve(100);  // 失敗時は例外

vector<int> v2 = {10, 20};
swap(v, v2);  // nothrow

実行結果

=== 基本保証 ===
Copied: 10, 20
Assigned: 10, 20

=== 強い保証 ===
After push_back: size = 4
Last element: 100

=== nothrow保証 ===
Before swap: a=10, b=20
After swap: a=20, b=10
is_nothrow_copy_constructible: 1
is_nothrow_move_constructible: 1

=== RAII ===
File written successfully
File handle automatically closed

=== ScopeGuard ===
Resource acquired: 1
Resource cleaned up by scope guard
After scope: resource = 0

=== トランザクション ===
Before: Alice=1000, Bob=500
After transfer(300): Alice=700, Bob=800
Transfer failed: Insufficient balance
State preserved: Alice=700, Bob=800

ベストプラクティス

  1. デストラクタはnoexcept: 例外を投げない
  2. swapはnoexcept: 強い保証の基礎
  3. ムーブ操作はnoexcept: パフォーマンスとSTL互換性
  4. RAII: リソース管理の基本
  5. copy-and-swap: 強い保証の実装パターン

まとめ

操作 推奨される保証
デストラクタ nothrow
swap nothrow
ムーブ nothrow
push_back strong
operator= strong(copy-and-swap)
10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?