C++の基礎 - スマートポインタ(unique ptr)

提供:MochiuWiki : SUSE, EC, PCB
2019年8月6日 (火) 22:17時点における192.168.1.23による版 (ページの作成:「== 概要 == C++11では、unique_ptr<T>、shared_ptr<T>、weak_ptr<T>の3種のスマートポインタが追加された。<br> これらのスマートポインタは…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
ナビゲーションに移動 検索に移動

概要

C++11では、unique_ptr<T>、shared_ptr<T>、weak_ptr<T>の3種のスマートポインタが追加された。
これらのスマートポインタは、メモリの動的確保の利用の際に生じる多くの危険性を低減する目的で使用されるが、
それぞれ独自の考え方と機能を持っている。

3種のスマートポインタを適切に使い分けることで、安全性と開発速度の向上が見込めるだけでなく、
プログラマの意図に合わせてポインタを記述し分けることができる、非常に強力なツールとなる。

ここでは、スマートポインタについて初めて学ぶ人を対象に、
C++11で追加された3種のスマートポインタの機能と使い方、および3種をどのように考えて使うかについて、初歩的な解説を行う。


unique_ptrとは

unique_ptr<T>は、あるメモリに対する所有権を持つポインタが、ただ一つであることを保証するようなスマートポインタである。
テンプレート引数で保持するポインタ型を指定し、スマートポインタが破棄される際にディストラクタにおいて自動的にメモリを開放する。

unique_ptr<T>は、以下の様な特徴を持っている。
・あるメモリの所有権を持つ unique_ptr<T>は、 ただ一つのみである。
・コピーが出来ない。代わりに、C++11で新たに追加されたムーブによって、所有権を移動することができる。
・通常のポインタに匹敵する処理速度。
・配列を扱うことができる。
・deleter(後述)を指定することができる。

 #include<iostream>
 #include<memory>    // スマートポインタを使用する時に指定する
 
 class hoge
 {
    private:
       std::unique_ptr<int> ptr;
 
    public:
       hoge(int val_) : ptr(new int(val_)){}
       int getValue()const{return *ptr;}
 };
 
 int main()
 {
    // hogeのコンストラクタでint型を動的に確保しunique_ptrに委ねる
    hoge Hoge(10);
 
    // コンストラクタの引数として、動的確保したメモリのアドレスを指定
    std::unique_ptr<int> ptr(new int(10));
 
    // reset関数を使用して後から代入できる
    std::unique_ptr<int> ptr2;
    ptr2.reset(new int(10));
 
    // C++14以降では、make_unique関数が使用できる
    std::unique_ptr<int> ptr3 = std::make_unique<int>(10);
 
    // unique_ptrはコピーコンストラクタで作成しようとすると、コンパイルエラーになる
    //hoge Hoge2(Hoge);
 
   // 明示的にmoveするならOK
   hoge Hoge2(std::move(Hoge));
 
   return 0;
 }



unique_ptrの使い方

詳しい使い方を下記に示す。
尚、使用の際には#include <memory>を指定する必要がある。

 // まず、unique_ptr<T>にメモリの所有権を委ねるには、コンストラクタで指定するかreset(pointer)を使う。
 // C++14以降では、make_unique<T>関数を使って作成することができる。
 
 // コンストラクタの引数として、動的確保したメモリのアドレスを指定
 std::unique_ptr<int> ptr(new int(10));
 
 // reset関数を使って、後から代入することもできる
 std::unique_ptr<int> ptr2;
 ptr2.reset(new int(10));
 
 // C++14以降であれば、make_unique関数を使うこともできる
 std::unique_ptr<int> ptr3=std::make_unique<int>(10);


 // unique_ptr<T>は、コピーは禁止されているが、move関数は使用することができる。
 std::unique_ptr<int> ptr(new int(10));
 
 // コピーコンストラクタや、コピー代入演算子はエラー
 //std::unique_ptr<int> ptr2(ptr); //===ERROR===
 //std::unique_ptr<int> ptr2;
 //ptr2 = ptr;
 
 // ムーブコンストラクタや、ムーブ代入演算子はOK
 // この時、所有権が移動する
 std::unique_ptr<int> ptr3(std::move(ptr)); // ptrの所有権がptr3に移動する
 
 std::unique_ptr<int> ptr4;
 ptr4 = std::move(ptr3);                   // ptr4の所有権がptr5に移動する


 // メモリの解放は、ディストラクタや reset(pointer)を使う。
 
 // 引数なしやnullptrを引数としてreset関数を呼んでも、明示的に解放できる
 std::unique_ptr<int> ptr(new int(10));
 ptr.reset();


 // 生のポインタが欲しい時は、get()かrelease()を使う
 
 // get()は、生ポインタを得るだけで、ポインタの所有権はunique_ptr<T>が保持し続ける
 int *pint;
 pint = ptr.get();
 
 // release()は、ポインタの所有権自体も放棄するため、メモリの開放は自分で行う必要がある
 pint = ptr.release();
 delete pint;


 // unique_ptr<T[]>のように指定すれば、配列を扱うこともできる
 // 配列型の場合、operator[](size_t)を使用することができる
 std::unique_ptr<int[]> ptrArray(new int[10]);
 
 for(int i = 0; i < 10; i++)
 {
    ptrArray[i] = i;
 }


 // ポインタの保持するメモリにアクセスするには、通常のポインタ同様に、operator*()やoperator->()が使用できる
 std::unique_ptr<std::string> pStr(new std::string("test"));
 
 // operator*()でstring型を参照(testと表示される)
 std::cout << *pStr << std::endl;        
 
 // operator->()で、string型のsize関数を呼び出せる
 unsigned int StrSize = pStr->size();