1. 導入
C++の現場において、ファイルハンドルやネットワークソケット、あるいはメモリバッファなどの「リソース」を管理する際、最も注意すべきなのが「二重解放(Double Free)」です。誤ってリソースをコピーしてしまうと、複数のオブジェクトが同じリソースを所有していると勘違いし、デストラクタで同じポインタを二度解放してクラッシュを招きます。本記事では、コピーを禁止し、移動(ムーブ)のみを許可することで、安全にリソースを管理する設計手法を解説します。
2. 基礎知識
C++のクラスには、デフォルトで「コピーコンストラクタ」と「代入演算子」が自動生成されます。これらはメンバ変数を単純にコピーするため、リソースのポインタが保持されている場合、同一メモリを指す複数のオブジェクトが生成されてしまいます。
これを防ぐのが「ムーブセマンティクス」です。所有権を「コピー(複製)」するのではなく「移動(所有権の移譲)」させることで、リソースの管理責任を常に一つに絞り込み、メモリ安全性を高めることができます。
3. 実装/解決策
所有権を持つべきクラスでは、コピーを明示的に禁止し、ムーブを許可する設定を行います。
具体的には、以下の手順を踏みます。
・コピーコンストラクタとコピー代入演算子を delete 指定して無効化する。
・ムーブコンストラクタとムーブ代入演算子を実装(または default 指定)する。
これにより、プログラマが意図せずコピーしようとした瞬間にコンパイルエラーが発生し、安全ではないコードの混入を未然に防ぐことができます。
4. サンプルプログラム
以下は、リソース管理用クラスの基本的な実装例です。
include
include
class ResourceManager {
private:
int data; // 管理対象のリソース(例:巨大配列)
public:
explicit ResourceManager(int val) : data(new int(val)) {
std::cout << "リソース確保: " << data << std::endl;
}
~ResourceManager() {
if (data) {
std::cout << "リソース解放: " << data << std::endl;
delete data;
}
}
// コピーを禁止する(コンパイルエラーを誘発させる)
ResourceManager(const ResourceManager&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
// ムーブコンストラクタ:所有権を移動し、元のオブジェクトを無効化する
ResourceManager(ResourceManager&& other) noexcept : data(other.data) {
other.data = nullptr; // 元の所有者のポインタを切り離す
std::cout << "リソース移動" << std::endl;
}
// ムーブ代入演算子
ResourceManager& operator=(ResourceManager&& other) noexcept {
if (this != &other) {
delete data; // 現在のリソースを解放
data = other.data; // 所有権を移譲
other.data = nullptr;
}
return this;
}
};
int main() {
ResourceManager res1(100);
// ResourceManager res2 = res1; // ← これを行うとコンパイルエラーとなり、安全を保証できる
ResourceManager res2 = std::move(res1); // 移動は可能
return 0;
}
5. 応用・注意点
現場での設計において、`std::unique_ptr` を活用するのも非常に有効な手段です。`unique_ptr` は内部的に上記のロジックを実装しており、コピー不可・移動可能という特性を強制します。自作クラスにリソースを持たせる場合、可能な限り `unique_ptr` でラップすることで、デストラクタの実装を省略でき、より堅牢なコードになります。
また、ムーブ操作を行う際は、移動元のオブジェクトが「有効だが特定不能な状態」になることに注意してください。移動後に `res1` を使用しないよう、コードレビューで意識的にチェックすることが重要です。

コメント