M.6 std::unique_ptr


https://www.learncpp.com/cpp-tutorial/stdunique_ptr/
章の冒頭でpointerの使用がバグやメモリの漏洩を引き起こす方法について説明しました.
たとえば、予期せぬ事前に関数を返すと、ポインタが正しく解放されない可能性があります.
#include <iostream>

void someFunction()
{
    auto* ptr{ new Resource() };

    int x{};
    std::cout << "Enter an integer: ";
    std::cin >> x;

    if (x == 0)
        throw 0; // the function returns early, and ptr won’t be deleted!

    // do stuff with ptr here

    delete ptr;
}
上記のfunctionでは、functionscopeを超えてptrが無効になっていないためメモリが漏洩する
moveの意味の基礎について議論した以上,スマートポインタクラスについて議論しよう.
インテリジェントポインタは、動的に割り当てられたオブジェクトを管理するためのクラスであることに注意してください.
スマートポインタは、他の特性を提供することもできます.
インテリジェントポインタの核心的な特性は、動的に割り当てられたリソースまたはオブジェクトを適切なタイミングで解除することであり、これが主な目標である.
このため、スマートポインタは決して動的に割り当てられません.
stackに割り当てるべきで、スマートポインタで動的割り当てを正しく使用することを保証します.
c++11は4つのスマートポインタを提供します
  • std::auto ptr(使用すべきではなく、c++17で削除)
  • std::唯一のptr(最も一般的なスマートポインタクラス)
  • std::shared_prt
  • std::weak_ptr
  • std::unique_ptr


    std::unique ptrは、std::auto ptrの代わりにc+11で使用されるクラスです.
    uniuqe ptrは、複数のオブジェクトではなく1つのオブジェクトにのみ使用する必要があります.
    Unique ptrでは、所有権を持つオブジェクトは1つだけです.
    他のオブジェクトと所有権を共有できません
    std::unique ptrヘッダで定義
    簡単な例を見てみましょう.
    #include <iostream>
    #include <memory> // for std::unique_ptr
    
    class Resource
    {
    public:
    	Resource() { std::cout << "Resource acquired\n"; }
    	~Resource() { std::cout << "Resource destroyed\n"; }
    };
    
    int main()
    {
    	// allocate a Resource object and have it owned by std::unique_ptr
    	std::unique_ptr<Resource> res{ new Resource() };
    
    	return 0;
    } // res goes out of scope here, and the allocated Resource is destroyed
    上記のプログラムではstd::unique ptrタイプのresがstackに割り当てられます.
    main関数から離れたscopeは消えます
    この時点で自動的に割り当てられたリソースが解除されます
    std::auto ptrとは異なり、std::unique ptrはmoveの意味を適切に実現しています.
    #include <iostream>
    #include <memory> // for std::unique_ptr
    #include <utility> // for std::move
    
    class Resource
    {
    public:
    	Resource() { std::cout << "Resource acquired\n"; }
    	~Resource() { std::cout << "Resource destroyed\n"; }
    };
    
    int main()
    {
    	std::unique_ptr<Resource> res1{ new Resource{} }; // Resource created here
    	std::unique_ptr<Resource> res2{}; // Start as nullptr
    
    	std::cout << "res1 is " << (static_cast<bool>(res1) ? "not null\n" : "null\n");
    	std::cout << "res2 is " << (static_cast<bool>(res2) ? "not null\n" : "null\n");
    
    	// res2 = res1; // Won't compile: copy assignment is disabled
    	res2 = std::move(res1); // res2 assumes ownership, res1 is set to null
    
    	std::cout << "Ownership transferred\n";
    
    	std::cout << "res1 is " << (static_cast<bool>(res1) ? "not null\n" : "null\n");
    	std::cout << "res2 is " << (static_cast<bool>(res2) ? "not null\n" : "null\n");
    
    	return 0;
    } // Resource destroyed here when res2 goes out of scope
    上位プログラムは以下の出力を与える
    Resource acquired
    res1 is not null
    res2 is null
    Ownership transferred
    res1 is null
    res2 is not null
    Resource destroyed
    std::unique ptrはmoveの意味のために設計されているため、レプリカを初期化したり割り当てたりすることはできません.
    unique ptrで管理されているコンテンツを移動する場合は、moveの意味を無条件に使用します.
    上記のプログラムではstd::moveで実現します
    std::moveはres 1をr-valueタイプに変換しています.
    これはcopy割り当てではなくmove割り当てです

    Accessing the managed object


    std::unique ptrはリロードオペレータ*とオペレータ->を実現
    これにより、管理中のコンテンツを参照できます.
    オペレータ*は、管理リソースの参照を返します.
    operator->管理中のリソースを返すポインタ
    std::unique ptrは常にobjectを管理しているわけではないことを覚えておいてください.
    また、objectがない空の状態でもインスタンス化できることを知っています.
    関連するunique ptrに所有権を渡す場合はnullptrを持つこともできます
    最初の2つのオペレータを使用する前に、unique ptrが空であるかどうかを確認する必要があります.
    幸いなことにstd::unique ptrをboolに変換するとtrueまたはfalseが空であるかどうかがわかります
    ここに例があります
    #include <iostream>
    #include <memory> // for std::unique_ptr
    
    class Resource
    {
    public:
    	Resource() { std::cout << "Resource acquired\n"; }
    	~Resource() { std::cout << "Resource destroyed\n"; }
    	friend std::ostream& operator<<(std::ostream& out, const Resource &res)
    	{
    		out << "I am a resource\n";
    		return out;
    	}
    };
    
    int main()
    {
    	std::unique_ptr<Resource> res{ new Resource{} };
    
    	if (res) // use implicit cast to bool to ensure res contains a Resource
    		std::cout << *res << '\n'; // print the Resource that res is owning
    
    	return 0;
    }
    上記のプログラムではif(res)暗黙type castingを用いてチェック
    出力は次のとおりです.
    Resource acquired
    I am a resource
    Resource destroyed
    上記のプログラムでは、演算子*を使用してResourceClass objectを参照します.
    オペレータ<<オーバーロードのためcoutを使用して直接出力中
    そして、出力でscopeを離れて破壊された様子を確認できます

    std::unique_ptr and arrays


    std::auto ptrとは異なり、std::unique ptrはスマートなクラスであり、スカラーdeleteと配列deleteを使用することができます.
    ただし、std::arrayまたはstd::vector(またはstd::string)のほとんどの場合、std::unique ptrはfixedarrayrまたはdynmaic array、c-stylestringと混合して使用するのがより良い選択です.
    c++14はstd::make unique()という関数を追加した.
    #include <memory> // for std::unique_ptr and std::make_unique
    #include <iostream>
    
    class Fraction
    {
    private:
    	int m_numerator{ 0 };
    	int m_denominator{ 1 };
    
    public:
    	Fraction(int numerator = 0, int denominator = 1) :
    		m_numerator{ numerator }, m_denominator{ denominator }
    	{
    	}
    
    	friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
    	{
    		out << f1.m_numerator << '/' << f1.m_denominator;
    		return out;
    	}
    };
    
    
    int main()
    {
    	// Create a single dynamically allocated Fraction with numerator 3 and denominator 5
    	// We can also use automatic type deduction to good effect here
    	auto f1{ std::make_unique<Fraction>(3, 5) };
    	std::cout << *f1 << '\n';
    
    	// Create a dynamically allocated array of Fractions of length 4
    	auto f2{ std::make_unique<Fraction[]>(4) };
    	std::cout << f2[0] << '\n';
    
    	return 0;
    }
    上記のように使用可能
    make unique()functionはオプションですが、直接作成や書き込みではなくmake uniqueを使用することをお勧めします.

    Best practice


    Use std::make_unique() instead of creating std::unique_ptr and using new yourself.

    The exception safety issue in more detail


    EXceptionに関係なく、先にスキップ

    Passing std::unique_ptr to a function


    functionに所有権を持たせるにはstd::unique ptrを使用してbyvalueを通過する必要があります.ちなみにcopyの意味は無効なのでstd::moveを使用してパラメータをr-valueに設定する必要があります
    #include <memory> // for std::unique_ptr
    #include <utility> // for std::move
    
    class Resource
    {
    public:
    	Resource() { std::cout << "Resource acquired\n"; }
    	~Resource() { std::cout << "Resource destroyed\n"; }
    	friend std::ostream& operator<<(std::ostream& out, const Resource &res)
    	{
    		out << "I am a resource\n";
    		return out;
    	}
    };
    
    void takeOwnership(std::unique_ptr<Resource> res)
    {
         if (res)
              std::cout << *res << '\n';
    } // the Resource is destroyed here
    
    int main()
    {
        auto ptr{ std::make_unique<Resource>() };
    
    //    takeOwnership(ptr); // This doesn't work, need to use move semantics
        takeOwnership(std::move(ptr)); // ok: use move semantics
    
        std::cout << "Ending program\n";
    
        return 0;
    }
    上のプログラムからstd::moveでptrをr-valueに生成しbyvalueを渡す
    これにより、paramterresを介して所有権を渡すことができます.
    したがって,関数のscopeが終わるにつれてResourceは破壊される.
    従って、出力は以下のようになる
    Resource acquired
    I am a resource
    Resource destroyed
    Ending program
    しかし、ほとんどの場合、関数にリソースの所有権を持たせません.
    passbyref転送は使用できますが、関数でリソースを変更する場合にのみpassbyrefを使用します.
    そうでなければ、resource自体をrefまたはpointerに渡すのがより良い選択です.
    これにより、functionは呼び出し者にリソースの管理方法を知らせることができます.
    std::unique ptrのraw pointerを購入したい場合はget()というメンバー関数を使用できます
    #include <memory> // for std::unique_ptr
    #include <iostream>
    
    class Resource
    {
    public:
    	Resource() { std::cout << "Resource acquired\n"; }
    	~Resource() { std::cout << "Resource destroyed\n"; }
    
    	friend std::ostream& operator<<(std::ostream& out, const Resource &res)
    	{
    		out << "I am a resource\n";
    		return out;
    	}
    };
    
    // The function only uses the resource, so we'll accept a pointer to the resource, not a reference to the whole std::unique_ptr<Resource>
    void useResource(Resource* res)
    {
    	if (res)
    		std::cout << *res << '\n';
    }
    
    int main()
    {
    	auto ptr{ std::make_unique<Resource>() };
    
    	useResource(ptr.get()); // note: get() used here to get a pointer to the Resource
    
    	std::cout << "Ending program\n";
    
    	return 0;
    } // The Resource is destroyed here
    したがって,上記のプログラムはresourceのポインタのみを渡す.
    出力は次のとおりです.
    Resource acquired
    I am a resource
    Ending program
    Resource destroyed

    std::unique_ptr and classes


    もちろん、std::unique ptrを設計したクラスのメンバーとして使用できます.
    この場合、クラス構造関数がダイナミックメモリを解放する心配はありません.std::unique ptrはクラスオブジェクトです.クラスが破壊されると自然に一緒に破壊されるからです.しかし,我々が設計したclass objectが動的に割り当てられると,当然異なる物語がある.

    Misusing std::unique_ptr


    unique ptrを使用するには2つのエラーがあります
    第一に、複数の唯一のptrに同じリソースを管理させないでください.
    Resource* res{ new Resource() };
    std::unique_ptr<Resource> res1{ res };
    std::unique_ptr<Resource> res2{ res };
    上記の場合、実行できますが、プログラムの最後にresを2回無効にしようとしたため、問題が発生しました.
    第二に、std::unique ptr objectを手動で削除しないでください.
    Resource* res{ new Resource() };
    std::unique_ptr<Resource> res1{ res };
    delete res;
    そうすれば、同じように復讐解除問題が発生する.