Modern C++編【標準ライブラリ】 第3章 unique_ptr

先頭へ戻る

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

概要

std::unique_ptrスマートポインタの一種で、確保されたリソースを指すポインタを占有管理します。スマートポインタは、【言語解説】第15章で取り上げていますが、リソースの管理を自動化するなどのスマート(賢い)な機能を持ち、ポインタとしての機能を備えたクラス(通常はクラステンプレート)です。

std::unique_ptr はクラステンプレートです。デストラクタで、リソースの解放を行うようになっているため、解放に関する処理を std::unique_ptr に一任しておけば、解放忘れや二重解放を防ぐことができます。

std::unique_ptr は、memory という標準ヘッダで、以下のように定義されています。

namespace std {
    template <typename T, typename D = std::default_delete<T>>
    class unique_ptr;

    template <typename T, typename D>
    class unique_ptr<T[], D>;
}

テンプレートパラメータ T が、管理するポインタが指す型です。テンプレートパラメータ D はデリータの指定です。デリータについては、「デリータ」の項で取り上げます。

2つある std::unique_ptr の定義のうち、1つ目のものは、管理対象のポインタが単独のリソースを指している場合に使うものです。2つ目のものは、リソースが配列になっている場合に使います。最もありふれた例でいえば、1つ目の方は new で得たポインタを管理し、2つ目の方は new[] で得られたポインタを管理します。

初期化と破棄

std::unique_ptr のコンストラクタに、生のポインタを渡すと、そのポインタが指しているリソースの解放を std::unique_ptr に任せることになります。言い換えると、std::unique_ptr がリソースの管理権限を持つことになります。このような権限を、所有権という言葉で表現します。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass()
    {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "Destructor" << std::endl;
    }
};

int main()
{
    std::unique_ptr<MyClass> p(new MyClass());
}

実行結果:

Constructor
Destructor

スマートポインタを使うと決めたなら(通常そうするべきです)、一瞬たりとも、スマートポインタに所有されていない瞬間を作らないようにしなければなりません。つまり、このサンプルプログラムのように、コンストラクタの実引数のところで生成を行うのです。次のような書き方をすると、瞬間的にはスマートポインタが所有していないタイミングが出来てしまうため、好ましくありません。

#include <memory>

int main()
{
    int* n = new int(100);
    std::unique_ptr<int> p(n);
}

実行結果:



例えば、n を得て、スマートポインタに引き渡すまでの間に他の処理がある(または追加される)と、解放忘れにつながる恐れがあります。また何より、例外(【言語解説】第18章)発生時に、確実に解放忘れにつながってしまいます。

std::unique_ptr のデフォルトコンストラクタは、何も管理していない状態(所有権を持たない状態)で初期化します。ヌルポインタを渡した場合も同様の状態になります。

std::unique_ptr にはコピーコンストラクタがありません。std::unique_ptr は、リソースを占有して管理するものなので、コピーが出来ると都合が悪いためです。一方で、ムーブコンストラクタは持っています。こちらは後で解説します

std::unique_ptr のデストラクタでは、デフォルトでは delete を使った解放を行います。この動作は、デリータの機能を使って変更することができます。

ポインタを取得する

管理している生のポインタは、getメンバ関数を使って取得することができます。この関数は、ポインタを管理していないときには nullptr を返します。

#include <iostream>
#include <memory>

int main()
{
    std::unique_ptr<int> p1(new int(100));

    int* rawPtr = p1.get();
    std::cout << *rawPtr << std::endl;

    std::unique_ptr<int> p2;
    rawPtr = p2.get();
    if (rawPtr == nullptr) {
        std::cout << "null" << std::endl;
    }
    else {
        std::cout << "not null" << std::endl;
    }
}

実行結果:

100
null

ポインタ操作

配列版でない std::unique_ptr は、生のポインタと同じように、*演算子や ->演算子を使うことができます。

#include <iostream>
#include <memory>

class MyClass {
public:
    explicit MyClass(int n) : mNum(n)
    {}

    inline void SetNum(int n)
    {
        mNum = n;
    }
    inline int GetNum() const
    {
        return mNum;
    }

private:
    int mNum;
};

int main()
{
    std::unique_ptr<MyClass> p(new MyClass(100));

    const MyClass& c = *p;
    p->SetNum(200);

    std::cout << p->GetNum() << std::endl;
    std::cout << c.GetNum() << std::endl;
}

実行結果:

200
200

*演算子は、管理しているポインタが指す先にあるものを左辺値参照で返します。いわゆるポインタの間接参照を実現します。->演算子は、管理しているポインタを通して、指す先にあるものを操作するときに使います。

当然、ポインタを管理していないときにこれらの操作を行うと、未定義の動作となってしまうので注意して下さい。ポインタを管理しているかどうかを調べるには、getメンバ関数が nullptr を返さないことを確認するか、bool型への型変換演算子(【言語解説】第9章)を利用して、以下のように問い合わせます。

// std::unique_ptr の p がポインタを管理しているか?
if (p) {}

ポインタを再設定する

resetメンバ関数を使うと、管理対象のポインタを変更できます。元々何らかのポインタを管理していた場合は、先に解放処理が行われます。

また、実引数が無い resetメンバ関数を呼ぶか、実引数に nullptr を指定すると、何も管理していない状態にすることができます。こちらも、元々何らかのポインタを管理していた場合は、解放処理が行われます。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass()
    {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "Destructor" << std::endl;
    }
};

int main()
{
    std::unique_ptr<MyClass> p(new MyClass());

    p.reset(new MyClass());

    p.reset();
}

実行結果:

Constructor
Constructor
Destructor
Destructor

所有権の変更

std::unique_ptr は、ムーブコンストラクタとムーブ代入演算子を持っており、所有権を移動させることができます。所有権を移動させるとは、移動元が管理していたポインタは nullptr に置き換えられ、移動先の std::unique_ptr に管理が移るということです。

#include <cassert>
#include <iostream>
#include <memory>
#include <utility>

class MyClass {
public:
    MyClass()
    {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "Destructor" << std::endl;
    }
};

int main()
{
    std::unique_ptr<MyClass> p1(new MyClass());

    std::unique_ptr<MyClass> p2 = std::move(p1);
    assert(!p1 && p2);

    p1 = std::move(p2);
    assert(p1 && !p2);
}

実行結果:

Constructor
Destructor

また、releaseメンバ関数を使うと、生のポインタを戻り値で返し、自身は所有権を手放します。言い換えると、std::unique_ptr から所有権を取り戻す関数ですが、当然、自動的な解放が行われなくなってしまうので、必ず戻り値を受け取って、解放まで面倒を見るようにしなければなりません。

#include <cassert>
#include <iostream>
#include <memory>
#include <utility>

class MyClass {
public:
    MyClass()
    {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "Destructor" << std::endl;
    }
};

int main()
{
    std::unique_ptr<MyClass> p(new MyClass());

    MyClass* c = p.release();  // 所有権を失う

    delete c;  // 自力で解放
}

実行結果:

Constructor
Destructor

デリータ

std::unique_ptr が行う解放処理は、メンバとして保持されているオブジェクトが行います。このオブジェクトは、関数として呼び出せるものでなければなりません(関数、関数オブジェクト)。なお、このオブジェクトのことを、デリータと呼びます。

std::unique_ptr の2つ目のテンプレートパラメータには、デリータの型が指定されます。特に指定しなければ、std::default_delete という標準のデリータが指定されます。これは、非配列のときは delete を、配列のときは delete[] を使って解放を行うように実装されています。

標準でないデリータを使う場合、その方法には幾つか選択肢があります。std::fopen() で得たファイルポインタを管理する例を取り上げます。

関数を使う

解放処理を記述した関数を定義して、その関数ポインタを std::unique_ptr のコンストラクタへ渡します。2つ目のテンプレート実引数には、関数の型を指定します。

#include <cstdio>
#include <memory>

int main()
{
    std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(std::fopen("test.bin", "r"), std::fclose);
}

実行結果:



テンプレート実引数に関数の型を指定する部分は少々面倒な場合もあります。decltype(【言語解説】第20章)を使うのが良いでしょう。

std::unique_ptr<std::FILE, decltype(&std::close)> fp(std::fopen("test.bin", "r"), std::fclose);

関数オブジェクトを使う

関数オブジェクト(【言語解説】第32章)を使う場合は、operator() で解放処理を行うように実装したクラスを定義します。std::unique_ptr の2つ目のテンプレート実引数に、クラスの型を指定すれば、解放時に operator() を呼び出してくれます。
この方法の場合、std::unique_ptr のコンストラクタに、管理してもらうポインタ以外のものを渡す必要はありません。

#include <cstdio>
#include <iostream>
#include <memory>

struct FileCloser {
    void operator()(std::FILE* fp)
    {
        std::cout << "call FileCloser()" << std::endl;
        std::fclose(fp);
    }
};

int main()
{
    std::unique_ptr<std::FILE, FileCloser> fp(std::fopen("test.bin", "r"));
}

実行結果:

call FileCloser()

ラムダ式を使う

関数オブジェクトが使えるのなら、ラムダ式(【言語解説】第32章)を使うことも考えられます。

#include <cstdio>
#include <iostream>
#include <memory>

int main()
{
    auto fileCloser = [](std::FILE* fp) {
        std::cout << "call lambda" << std::endl;
        std::fclose(fp);
    };

    std::unique_ptr<std::FILE, decltype(fileCloser)> fp(std::fopen("test.bin", "r"), fileCloser);
}

実行結果:

call lambda

テンプレート実引数にデリータの型名を指定しなければならないため、一旦、変数にラムダオブジェクトを受け取る形になります。

配列版の unique_ptr

配列に対応した std::unique_ptr では、1つ目のテンプレート実引数に指定する型を配列型にします。

#include <cassert>
#include <iostream>
#include <memory>
#include <utility>

class MyClass {
public:
    MyClass()
    {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "Destructor" << std::endl;
    }
};

int main()
{
    std::unique_ptr<MyClass[]> p(new MyClass[5]);

    const MyClass& r = p[3];  // []演算子が適用できる
}

実行結果:

Constructor
Constructor
Constructor
Constructor
Constructor
Destructor
Destructor
Destructor
Destructor
Destructor

配列版の std::unique_ptr では、[]演算子が使用できるようになっています。指定した位置にある要素を指す参照が返されます。範囲外アクセスは未定義の動作です。

実際のところ、動的に管理する配列が必要であれば、std::vector(第7章)や std::basic_string(第9章)を使うことを検討した方が良いです。これらは配列を便利に扱うための機能を備えている上に、適切なメモリ管理も行います。

C++14 (std::make_unique())

C++14

C++14 からは、std::unique_ptr のオブジェクトを生成する際のヘルパーとなる関数、std::make_unique() が追加されています。色々な利点がある関数なので、C++14以降ならば、基本的にこれを使うようにした方が良いでしょう。

std::make_unique() は関数テンプレートになっており、std::unique_ptr に管理してもらうオブジェクトを関数内で生成し、その所有権を持った std::unique_ptr を生成して返します。

std::make_unique() は、次のように宣言されています。

namespace std {
    template <typename T, typename... Args>
    unique_ptr<T> make_unique(Args&&... args);

    template <typename T>
    unique_ptr<T> make_unique(size_t n);

    template <typename T, typename... Args>
    /*unspecified*/ make_unique(Args&&...) = delete;
}

1つ目の宣言は、生成するオブジェクト(std::unique_ptr に管理してもらうオブジェクト)のコンストラクタに渡す実引数を、std::make_unique() の実引数に指定するタイプです。可変個引数テンプレート(【言語解説】第30章)を使っているため、引数を何個でもどんな型でも渡すことができます。

2つ目の宣言は、配列版の std::unique_ptr のために存在するタイプです。実引数に、配列の要素数を指定すると、その個数分のオブジェクトを持つ配列を生成し、この配列を管理する std::unique_ptr を返します。

3つ目の宣言は、=delete が付いているので使用できません。これは、配列版の std::unique_ptr で、1つ目の宣言の形式を使えなくするものです。配列を動的に確保するときには、デフォルトコンストラクタを使うしかありませんから、実引数を渡すタイプは使用できなくなっています。

#include <cassert>
#include <iostream>
#include <memory>
#include <utility>

class MyClass {
public:
    MyClass()
    {
        std::cout << "Constructor" << std::endl;
    }
    MyClass(int a, double b)
    {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "Destructor" << std::endl;
    }
};

int main()
{
    std::unique_ptr<MyClass> p1 = std::make_unique<MyClass>(10, 5.5);
    std::unique_ptr<MyClass[]> p2 = std::make_unique<MyClass[]>(5);
}

実行結果:

Constructor
Constructor
Constructor
Constructor
Constructor
Constructor
Destructor
Destructor
Destructor
Destructor
Destructor
Destructor

ポイントとして重要なのは、std::make_unique() を使うと、我々が書くコードから、new が見えなくなることです。new を行うのは std::make_unique() の役目となります。このおかげで、万が一にも解放し損ねる可能性が無くなります。例えば、次のようなコードは、解放し損ねる可能性があります。

#include <memory>

void f(std::unique_ptr<int> a, std::unique_ptr<int> b);

int main()
{
    f(std::unique_ptr<int>(new int(10)), std::unique_ptr<int>(new int(20)));
}

この場合、コンパイラの実装によっては、std::unique_ptr のコンストラクタが呼び出される前に、2つの new が呼び出される可能性があります。つまり、「new → new → std::unique_ptr のコンストラクタ → std::unique_ptr のコンストラクタ」という順番で実行され得るということです。すると、もし2つ目の new が失敗して例外(【言語解説】第18章)が送出されると、1つ目の new が std::unique_ptr の管理下になっていないため、解放の機会を失ってしまいます。
std::make_unique() を使えば、new が関数内部に隠されるので、実行順序の問題を解決できます。

#include <memory>

void f(std::unique_ptr<int> a, std::unique_ptr<int> b);

int main()
{
    f(std::make_unique<int>(10), std::make_unique<int>(20));
}

また、std::make_unieue() を使うと、コンパイラが戻り値の型を判断することができますから、次のサンプルのように、戻り値は auto型の変数で受け取ることができます。

auto p1 = std::make_unique<MyClass>(10, 5.5);
auto p2 = std::make_unique<MyClass[]>(5);

1つの文の中で、「MyClass」のような型名の指定を繰り返し書かなくてよくなり、コードの重複を防ぐ効果があります。勿論、単純にコード量が減ることも利点と言えるでしょう。

std::make_unique() の欠点は、デリータを指定できないことです。デリータを変更する必要がある場合は諦めなければなりません。


練習問題

問題① std::unique_ptr がコピーできないことを確かめて下さい。また、コピーができない理由を説明して下さい。

問題② 次のコードを std::unique_ptr を使った形に書き換えて下さい。Create() は生成の具体的な処理を隠し、抽象化する目的で存在している関数であり、引き続き使用しなければならないものとします。

class MyClass {};

MyClass* Create()
{
    return new MyClass();
}

int main()
{
    MyClass* p = Create();
}

問題③ 実引数がヌルポインタかどうかを判定して、標準出力へ結果を出力する関数を考えます。生のポインタでもスマートポインタでも使えるように、関数を実装して下さい。

void PrintNullOrNotNull(/* */)
{
    if (/* */) {
        std::cout << "not null" << std::endl;
    }
    else {
        std::cout << "null" << std::endl;
    }
}


解答ページはこちら

参考リンク

更新履歴

'2017/10/11 新規作成。



前の章へ

次の章へ

Modern C++編のトップページへ

Programming Place Plus のトップページへ