【C++】std::unique_ptr の使い方 | メモリ管理を自動化するスマートポインタ

目次

はじめに

C++で new を使って動的にメモリを確保した場合、必ず delete を使って手動でメモリを解放しなければならず、もし delete を忘れるとメモリリークの原因になります。

この問題を解決するのが、C++11で導入されたスマートポインタの一つ、std::unique_ptr です。unique_ptr は、RAII(Resource Acquisition Is Initialization)という原則に基づき、オブジェクトのライフタイム管理を自動化します。

  • unique_ptr生成される際に、メモリ(リソース)の所有権を得る。
  • unique_ptr がスコープを抜けて破棄される際に、そのデストラクタが自動的に delete を呼び出し、リソースを解放する。

この記事では、unique_ptr の3つの主要な使い方を解説します。

  1. 基本的な使い方(自動的なメモリ解放)
  2. 所有権の移動 (std::move)
  3. カスタムデリータの指定

【前提】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 を行った後、元のポインタ(p1p2)は空になり、リソースを管理しなくなります。所有権は常にただ一つの 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++におけるメモリリークを防ぐための最も基本的で強力な方法です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次