C +におけるスマートポインタを用いたメモリ管理-パート1



C +におけるスマートポインタを用いたメモリ管理-パート1
CとC +の両方の主要な長所と短所の1つはポインタです.メモリ管理が正しく実行されない場合、C +はポインタの多くの利点を持っています.ポインタには、参照するリソースの明示的な管理が必要です.たとえば、動的に割り当てられたメモリのアドレスを保持するポインタを使用して取得するnew プログラマがメモリを解放する必要があるdelete ; そうすることができないので、見つけるのが難しいポインターまたはメモリリークをぶらぶらさせます.
スマートポインターは、リソースの明示的な管理を自動化するメカニズムを提供します.rawポインタとは対照的に、スマートポインタは、それが指すリソースのメモリを管理することができます.それはリソースへの生のポインタのためのプロキシです、そして、それは生のポインタのように見えて、感じます.Dereferencingをサポートします(*) とポインタ(->) 演算子スマートポインタはスタックにあるスコープから出ると、そのデストラクタはそれが指すダイナミックメモリを解放する.
スマートポインタはオブジェクト指向言語の熟語リソース取得を初期化するRAII ). このイディオムは、リソースが初期化時間、すなわちポインタのメモリが作成されたときに取得されると述べます.

なぜスマートポインタを使うべきか
メモリリークを引き起こすこの非常に単純なコードを見てみましょう.
int main()
{
    int *ptr = new int[20];

    // Oops forgot to delete ptr
    return 0;
}
私が使うときValgrind メモリリークのバイナリをチェックする次の結果を得た.
==1562402== LEAK SUMMARY:
==1562402==    definitely lost: 80 bytes in 1 blocks
==1562402==    indirectly lost: 0 bytes in 0 blocks
==1562402==      possibly lost: 0 bytes in 0 blocks
==1562402==    still reachable: 0 bytes in 0 blocks
==1562402==         suppressed: 0 bytes in 0 blocks
==1562402==
==1562402== For lists of detected and suppressed errors, rerun with: -s
==1562402== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
私たちが走っていなかったので、メモリの80バイト(4バイト×20)をリークしたことをはっきりと見ることができますdelete on ptr そして、それは手動ですべての割り当てとdelete 右側の時間;特に、オブジェクトがコピーされ、コード内のどこにでも移動して渡されたC +プロジェクトが十分に大きい場合.
生のポインタを使ってメモリリークの別のシナリオを見てみましょう
void someFunction()
{
     MyClass *obj = new MyClass(10);
     if (!Validate(obj))
     {
    return; // memory leak
     }

     // ....

     obj->someOtherFunction();
     delete obj;
}
機能を仮定するsomeFunction() は無限回と呼ばれるobj 検証は毎回失敗します.これは、プログラマが新しいメモリを割り当てて、ポインタを失うことはないが、プログラムがもう使用しないオブジェクトへのポインタを保持している状態になる.これは正式にメモリリークではありませんが、同じ状況になります
使ってもnew and delete ペアが正しくメモリリークのシナリオに遭遇する可能性があります.以下の例を見てみましょう.
void SomeMethod()
{
   MyClass *obj = new MyClass;
   SomeOtherMethod();      // it can throw an exception
   delete obj;
}
例外がスローされた場合はobj オブジェクトは削除されません.どれだけ努力しても、すべての動的に割り当てられたメモリを解放することは非常に困難です.たとえ我々がそれをすることができるとしても、それはしばしば例外から安全でありません.
配列の削除delete
動的なサイズの一時的な配列を作成することがしばしば必要です.それらがもはや要求されないあと、割り当てられたメモリを解放することは重要です.C +は特別ですdelete 演算子[] ブラケットは、非常に簡単に忘れています.The delete[] 演算子は配列に割り当てられたメモリを削除するとともに、配列からすべてのオブジェクトのデストラクタを呼び出します.を使うのも不正確ですdelete 演算子なしで[] 配列のためのブラケット.

できるだけヒープメモリを使うのを避けるべきです
そして、これらの理由のために、現代のC +はスタックの上でオブジェクトを宣言することによってできるだけヒープメモリを使用するのを避けます;リソースを所有するスタックオブジェクトがスコープ外になると、そのデストラクタが自動的に呼び出されます.
単純なオブジェクトを示す例を次に示しますe . これは関数のスタックで宣言され、関数ブロックの最後に破棄されます.オブジェクトe リソースを所有していない(ヒープ割り当てされたメモリなど)はないので、Entity デストラクタ.
class Entity
{
private:
    int* data;
public:
    explicit Entity(const int size) { data = new int[size]; } // acquire
    ~Entity() { delete[] data; } // release
    void do_something() {}
};

void functionUsingEntity() {
    Entity e(100);   // lifetime automatically tied to enclosing scope

    e.do_something();

} // automatic destruction and deallocation for e
C++ 11以降は、標準ライブラリからのスマートポインタを使用して、前の例を書くより良い方法があります.スマートポインタは、所有しているメモリの割り当てと削除を管理します.スマートポインタを使用すると、Entity クラス.

スマートポインタの種類
スマートポインタの3種類があります.

  • ユニークなポインタstd::unique_ptr<> )
  • 専属所有
  • 軽量ポインター
  • スコープから出るときにオブジェクトを破壊する

  • 共有ポインタstd::shared_ptr<> )
  • 共有所有権
  • 最後の参照が解放されたときにオブジェクトを破棄する

  • 弱ポインタstd::weak_ptr<> )
  • 循環参照を中断する
  • 使用shared_ptr
  • NOTE: auto pointer (std::auto_ptr) has been depreciated after the inclusion of move semantics in C++11.



    ユニークなポインタstd::unique_ptr<> )
    unique_ptr , 割り当てられたオブジェクト、およびunique_ptr スコープから出て、ポインターオブジェクトは削除されます.この関数は、関数のままになっているかどうかにかかわらず、どこかにスローされるか、あるいは例外がスローされるかによって起こります.
    例えば、
    void functionWithUniquePointer()
    {
        std::unique_ptr<MyClass> ptr(new MyClass); // ptr owns the MyClass resource
        ptr->do_something();               // tell the object to do something
        do_somethingelse();                // might throw an exception
    } // ptr gets destroyed; destructor deletes the MyClass object resource
    

    使用unique_ptr 生のポインタのように
    デリフェレンシング以来(*) とポインタ(->) 演算子はunique_ptr クラス利用可能unique_ptr AS
    up->do_something(); // used arrow operator similar to raw pointers
    (*up).do_something(); // used dereferencing operator similar to raw pointers
    
    また、rawポインタに似たポインタのヌル性をチェックすることもできます
    if(up)
    if(up.get() != nullptr)
    if(up == nullptr)
    
    メモリの解放unique_ptr to nullptr
    up == nullptr
    up.reset()
    

    オブジェクトの排他的所有権unique_ptr資源の唯一の所有者であることは、それぞれunique_ptr ポインタは、それがもはや必要でない(すなわち、オブジェクトの寿命について責任がある)とき、オブジェクトを削除するのに責任があります.これは、他の誰もその生涯を終えて、同じ理由のためにオブジェクトを使うことができないことを意味しますstd::unique_ptr 実際にオーバーヘッドはありませんし、非常に予測可能な行動をしています.
    以来unique_ptr リソースの唯一の所有権を維持します、1つはそれのコピーを作ることができません、そして、タイプ安全性理由でコピー建設者とコピー代入演算子は削除されます.代わりに、ポインターの所有権を1つのポインターオブジェクトから別のものへ移動移動することもできます.
    std::unique_ptr<Thing> p1 (new Thing); // p1 owns the Thing
    std::unique_ptr<Thing> p2(p1); // error - copy construction is not allowed.
    std::unique_ptr<Thing> p3; // an empty unique_ptr;
    p3 = p1; // error, copy assignment is not allowed.
    
    std::unique_ptr<Thing> up1;
    std::unique_ptr<Thing> up2(new Thing);
    up1 = std::move(up2); // ownership transfer using move semantics is allowed
    

    通過std::unique_ptr 関数引数として
    エーunique_ptr を取る関数に渡す間に移動する必要がありますunique_ptr 値によって.
    void foo(std::unique_ptr<std::string> cp) {
     //...
    }
    
    //call foo
    
    auto up = std::make_unique<std::string>("some string");
    
    foo(up); //!ERROR. No copy allowed.
    
    foo(std::move(up)); //OK. Explicit move
    
    foo(std::make_unique<std::string>("some string")); //OK. Implicit move
    

    使用std::unique_ptr 標準コンテナで
    我々は標準的な容器を埋めることができますunique_ptr sはそれ自身のオブジェクト、そして所有権はそれからコンテナに有効に存在する.
  • あなたは、RValueを供給することによって容器を満たす必要がありますunique_ptr そのため、所有権はunique_ptr インザコンテナ
  • コンテナ内の項目を消去すると、unique_ptr .
  • あなたがコンテナ項目から所有権を移すならば、空unique_ptr sは容器にとどまります.

  • カスタム削除std::unique_ptr完全なタイプstd::unique_ptr 番目のテンプレートパラメータ、デフォルトの型を持つその削除子を持ちますstd::default_delete<T>
    template< class T, class Deleter = std::default_delete<T> > 
    class unique_ptr;
    
    エーunique_ptr オブジェクトリソースを破壊するために呼び出されるカスタム提供されたカスタムDeleterで宣言されて、初期化されることができます.
    template <typename T>
    class CustomDeleter
    {
    public:
        void operator()(T* ptr) const {
            std::cout << "freeing memory using 'delete'...\n";
            delete ptr;
        }
    };
    
    template <typename T>
    class CustomDeleter<T[]>
    {
    public:
        template <typename U>
        void operator()(U* ptr) const {
            std::cout << "freeing memory using 'delete[]'...\n";
            delete[] ptr;
        }
    };
    
    int main()
    {
    
        int* p = new int{0};
        int* p2 = new int[3]{5, 7, 8};
        std::unique_ptr<int, CustomDeleter<int>> upi(p, CustomDeleter<int>{}); // using unique_ptr with raw pointer is discouraged
        std::unique_ptr<int[], CustomDeleter<int[]>> upari(p2, CustomDeleter<int[]>{});
        return 0;
    }
    
    CustomDeleterはコンストラクタへの引数として渡され、unique_ptr オブジェクト.デクリプタは、関数オブジェクト、関数ポインタ、またはラムダであり得る.上記の例では、関数オブジェクトを2番目のパラメーターとして使用します.
    関数ポインタを使用した顧客の削除のための非常に簡単な例です.
    void deleter(int* ptr)
    {
         std::cout << "Function pointer deleter\n" << std::endl;
         delete ptr;
    }
    
    int main()
    {
         std::unique_ptr<int, void(*)(int*)> up_fptr(new int, deleter);
         return 0;
    }
    
    カスタム削除
    int main()
    {
         // With a capture-less lambda. 
         auto l = [](int* p) {
            std::cout << "Lambda deleter\n";
            delete p;
        };
         std::unique_ptr<int, decltype(l)> up_lambda(new int, l);
         return 0;
    }
    
    最も一般的な使用unique_ptr 関数に割り当てられたオブジェクトが削除されたことを確認する、かなり愚かな証明方法です.しかし、オブジェクトの所有権が移される必要がある状況があります、しかし、常に1つの場所に一度に残る;unique_ptr この概念を直接表現する方法を提供します.

    次は何ですか.
    次の記事ではstd::shared_ptr and std::weak_ptr 詳しくはまた、パフォーマンスに関してこれらのスマートポインタについて話します.