std::unique_ptr (c++11)

std::unique_ptr です。auto_ptr が deprecated(廃止予定)になりましたので、今後は shared_ptr, unique_ptr がスマートポインタの主流になっていくことでしょう。
auto_ptr の後継という意味では unique_ptr の方がより近いかもしれません。

unique_ptr は下記2つの形式をサポートします。

  1. template<class T, class Deleter = std::default_delete<T>> class unique_ptr;
  2. template <class T, class Deleter> class unique_ptr<T[], Deleter>;
    -> 配列T[]をサポートします。

 

 

 

 

評価環境:

 

1. まずは unique_ptr を使ってみる

まずは unique_ptr を使ってみます。shared_ptr とほとんど同じ感じで使用できますが、コピーはできません。moveを使って管理者の譲渡と明示的に行う必要があります。

#include <iostream>         // cout, endl
#include <string>           // string
#include <memory>           // unique_ptr

using namespace std;

// class Person
class Person{
private:
    string  name_;
    string  phone_;

public:
    // コンストラクタ
    Person()
        : Person("", "")           // 委譲コンストラクタ。VS2013以降で使用可能
//      : name_(""), phone_("")    // VS2013以前はこちらで
    {
    }
    // コピーコンストラクタ
    Person(const Person& rhs)
        : Person(rhs.name_, rhs.phone_)         // 委譲コンストラクタ。VS2013以降で使用可能
//      : name_(rhs.name_), phone_(rhs.phone_)  // VS2013以前はこちらで
    {
    }
    Person(string name, string phone)
        : name_(name), phone_(phone)
    {
        cout << "Person::Person()" << endl;
    }

    // デストラクタ
    virtual ~Person()
    {
        cout << "Person::~Person()" << endl;
    }

    // アクセッサ
    const std::string getName() const
    {
        return name_;
    }
    const std::string getPhone() const
    {
        return phone_;
    }
};


// グローバルな演算子オーバーロード */
std::ostream& operator<<(std::ostream& os, const unique_ptr<Person> &rhs)
{
    return (os << '(' << rhs->getName() << ',' << rhs->getPhone() << ')');
}

// Person をコンソール出力
void PrintPerson(unique_ptr<Person>& person)
{
    cout << person << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    /*========*/
    /* 前処理 */
    /*========*/
    unique_ptr<Person> foo(new Person("foo", "090-****-0123"));
    unique_ptr<Person> bar(new Person("bar", "090-****-5555"));

    /*========*/
    /* 処理部 */
    /*========*/
    PrintPerson(foo);
    PrintPerson(bar);

    /*========*/
    /* 後処理 */
    /*========*/
    return EXIT_SUCCESS;
}

 

2. unique_ptr で動的配列を使う

unique_ptr で動的配列を使用する例です。
T[]の特殊化が組み込まれており、[]演算子も実装されています。直観的で気持ちよく使えます。
ただし範囲外アクセスに対するチェック機構はありません。配列を使用したい場合、まずはコンテナの使用を検討するべきです。時々実行速度を問題にコンテナの使用を止めている場合がありますが、C++11で採用された std::array を使用すれば処理速度の問題もありません。

#include <iostream>         // cout, endl, EXIT_SUCCESS
#include <string>           // string
#include <memory>           // unique_ptr

using namespace std;

int main(int argc, char* argv[])
{
    // std::unique_ptr の例
    // default_delete の特殊化宣言は不要。delete[]が実行される。
    // []演算子もある。
    size_t  n = 10;
    std::unique_ptr<int[]> data(new int[n]);

    cout << "std::unique_ptr<int> : ";
    for (size_t i = 0; i < n; ++i){
        data[i] = i;
    }
    for (size_t i = 0; i < n; ++i){
        cout << data[i] << " ";
    }
    cout << endl;

    return EXIT_SUCCESS;
}

 

3. malloc, free を unique_ptr に置き換える

c 言語で規定されている malloc, calloc でメモリを動的に取得した場合、解放に使用する関数は delete や delete[] ではなく free を使用します。デアロケータを指定することで unique_ptr のメモリ解放を free で行えるようにします。

#include <iostream>     // cout, EXIT_SUCCESS
#include <memory>       // shared_ptr, unique_ptr
#include <cstdlib>      // malloc, free

using namespace std;

int main()
{
    const size_t    n = 256;

    // unique_ptr<int[]>
    std::unique_ptr<int[], decltype(&free)> memory(static_cast<int*> (malloc(n*sizeof(int))), std::free);    // "decltype(&free)" は "void(*)(void*)" となるみたい
    // std::unique_ptr<int[], void(*)(void*)> memory(static_cast<int*> (malloc(n*sizeof(int))), std::free);
    if (memory.get()==nullptr){
        // malloc失敗
    }
    else{
        // malloc成功
        for (size_t i = 0; i < n; ++i){
            memory[i] = (int)i;
        }
        for (size_t i = 0; i < n; ++i){
            cout << memory[i] << ", ";
        }
        cout << endl;
    }

    return EXIT_SUCCESS;
}

 

4. fopen, fclose を unique_ptr に置き換える

これは実装例をいくつか書いてみます。しっくりくるやつを使ってみてください。

4-1. シンプルな実装例

シンプルにやるとこんな感じでしょうか。

#include <stdio.h>  // fopen, fclose
#include <stdlib.h>
#include <memory>   // unique_ptr

#pragma warning(disable : 4996) // fopen, fclose の使用がエラーになるため

void custom_fclose(FILE* fp){
    if (fp != nullptr){
        fclose(fp);
    }
}

int main(int argc, char* argv[])
{
    std::unique_ptr<FILE, decltype(&custom_fclose)>   fp(fopen("fopen_test.txt", "w"), custom_fclose);
    // std::unique_ptr<FILE, void(*)(FILE*)>   fp(fopen("fopen_test.txt", "w"), custom_fclose);

    if (fp.get() != nullptr){
        // fopen成功
        fprintf(fp.get(), "test\n");
    }

    return EXIT_SUCCESS;
    // ここで custom_fclose が自動実行されます
}

 

4-2. Lambda式 による実装例

Lambda式 で記載するとこんな感じでしょうか。

#include <stdio.h>          // FILE, fopen, fclose
#include <stdlib.h>
#include <memory>           // unique_ptr

#pragma warning(disable : 4996)

int main(int argc, char* argv[])
{
    std::unique_ptr<FILE, void(*)(FILE*)>   fp(fopen("fopen_test.txt", "w"), [&](FILE* fp){
        if (fp != nullptr){
            fclose(fp);
        }
    });

    // 一応ファイルオープンの成功/失敗を確認してからファイルを使用
    if (fp.get() != nullptr){
        // fopen成功
        fprintf(fp.get(), "test\n");
    }

    return EXIT_SUCCESS;
    // ここで custom_fclose が自動実行されます
}

 

4-3. ファクトリーパターン による実装例

ファクトリーパターン的に unique_ptr を返す関数を作成するならばこんな感じでしょうか。

#include <cstdio>       // fopen, fclose
#include <iostream>     // cout, EXIT_SUCCESS
#include <memory>       // unique_ptr

using namespace std;

#pragma warning(disable : 4996)

std::unique_ptr<std::FILE, void(*)(FILE*)> make_file(const char * filename, const char * flags)
{
    // fclose に NULL を渡して実行してはいけない。
    // このため下記のような分岐処理が必要となる。
    std::FILE * const fp = std::fopen(filename, flags);
    return fp ? std::unique_ptr<std::FILE, void(*)(FILE*)>(fp, std::fclose) : std::unique_ptr<std::FILE, void(*)(FILE*)>();
}

int main()
{
    auto fp = make_file("hello.txt", "wb");

    // fp.get() をチェック
    // fopen に失敗していたら fp.get() は NULL
    if (fp.get()){
        fprintf(fp.get(), "Hello world.");
    }
}

私的には Lambda式 を使った2番目のやつが一番シンプルで好きかな。

 

5. make_unique

c++14 で make_unique が追加されました。Visual Studio は 2013 から対応しているようです。
unique_ptr を使用する場合、いろいろな理由から make_unique を使用することが推奨みたいです。

では早速「1. まずは unique_ptr を使ってみる」で記載したコードを make_unique を使って書き換えてみます。

ついでに、実行途中で unique_ptr 変数を途中で差し替える SwapTest() という関数を追加してみます。
関数 SwapTest() を終了するところで古いインスタンスが解放されてデストラクタが実行されます。

 

■評価環境

■参考URL

 

#include <tchar.h>      // _TCHAR, _tmain
#include <iostream>     // cout, endl
#include <string>       // string
#include <memory>       // unique_ptr

using namespace std;

class Person {
private:
    string  name_;
    string  phone_;

public:
    Person()
        : Person("", "")                // 委譲コンストラクタ、VS2013 OK、VS2012 NG
//      : name_(""), phone_("")
    {
    }
    Person(const Person& rhs)                 // コピーコンストラクタ
        : Person(rhs.name_, rhs.phone_) // 委譲コンストラクタ、VS2013 OK、VS2012 NG
//      : name_(rhs.name_), phone_(rhs.phone_)
    {
    }
    Person(string name, string phone)
        : name_(name), phone_(phone)
    {
        cout << "Person::Person()" << endl;
    }

    virtual ~Person()
    {
        cout << "Person::~Person()" << endl;
    }

    const std::string getName() const
    {
        return name_;
    }
    const std::string getPhone() const
    {
        return phone_;
    }
};


// グローバルな演算子オーバーロード */
std::ostream& operator<<(std::ostream& os, const unique_ptr<Person>& rhs)
{
    return (os << '(' << rhs->getName() << ',' << rhs->getPhone() << ')');
}

// Person をコンソール出力
void PrintPerson(unique_ptr<Person>& person)
{
    cout << person << endl;
}

void SwapTest(unique_ptr<Person>& person)
{
    person = make_unique<Person>("bar_2", "090-****-????");
}

int _tmain(int argc, _TCHAR* argv[])
{
    /*========*/
    /* 前処理 */
    /*========*/
    unique_ptr<Person> foo = make_unique<Person>("foo", "090-****-0123");
    unique_ptr<Person> bar = make_unique<Person>("bar", "090-****-5555");

    /*========*/
    /* 処理部 */
    /*========*/
    PrintPerson(foo);
    PrintPerson(bar);

    SwapTest(bar);

    PrintPerson(bar);

    /*========*/
    /* 後処理 */
    /*========*/
    return EXIT_SUCCESS;
}

 

[実行結果]

 

 

6. make_unique 配列版

c++14 で追加された make_unique は 配列型T に対応しています。Visual Studio は 2013 から対応しているようです。
make_share は本文記載時点では 配列型T に未対応です。 c++20 で対応予定、VS2019 ver.16.3.6 でも未対応です。
 unique_ptr を使用する場合、いろいろな理由から make_unique を使用することが推奨みたいです。

では同様に「1. まずは unique_ptr を使ってみる」で記載したコードを make_unique を使って書き換えてみます。

■評価環境

■参考URL

 

#include <tchar.h>      // _TCHAR, _tmain
#include <iostream>     // cout, endl
#include <string>       // string
#include <memory>       // unique_ptr

using namespace std;

// class Person
class Person {
private:
    string  name_;
    string  phone_;

public:
    Person()
        : Person("", "")                // 委譲コンストラクタ、VS2013 OK、VS2012 NG
//      : name_(""), phone_("")
    {
    }
    // コピーコンストラクタ
    Person(const Person& rhs)           // コピーコンストラクタ
        : Person(rhs.name_, rhs.phone_) // 委譲コンストラクタ、VS2013 OK、VS2012 NG
//      : name_(rhs.name_), phone_(rhs.phone_)
    {
    }
    Person(string name, string phone)
        : name_(name), phone_(phone)
    {
        cout << "Person::Person(), name_ = " << name_ << endl;
    }

    // デストラクタ
    virtual ~Person()
    {
        cout << "Person::~Person(), name_ = " << name_ << endl;
    }

    Person& operator=(const Person& rhd) {
        name_ = rhd.name_;
        phone_ = rhd.phone_;
        cout << "operator=, name_ = " << name_ << endl;

        return *this;
    }

    const std::string getName() const
    {
        return name_;
    }
    const std::string getPhone() const
    {
        return phone_;
    }
};

// グローバルな演算子オーバーロード */
std::ostream& operator<<(std::ostream& os, const Person& rhs)
{
    return (os << '(' << rhs.getName() << ',' << rhs.getPhone() << ')');
}

// Person をコンソール出力
void PrintPerson(Person& person)
{
    cout << person << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    const size_t    size_n = 3;
    unique_ptr<Person[]> persons = make_unique<Person[]>(size_n);

    persons[0] = Person("foo", "090-****-0123");
    persons[1] = Person("bar", "090-****-5555");
    persons[2] = Person("baz", "090-****-6666");

    for (int i = 0; i < size_n; ++i) {
        PrintPerson(persons[i]);
    }

    return EXIT_SUCCESS;
}

[実行結果]

無駄なコンストラクタ、デストラクタ、の発生が気になりますが、この動作で正しいです。

 

 

ライセンス

本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。

The MIT License (MIT)

Copyright © 2014-2022 Kinoshita Hidetoshi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 

 


参考

 


 

変更履歴

2022-07-13 - ページデザイン更新
2019-11-02 - 「6. make_unique 配列版」を追加
2019-10-26 - 「5. make_unique」を追加
2014-03-09 - 新規作成

 

Programming Items トップページ

プライバシーポリシー