3.スマートポインタのunique_ptr


3.スマートポインタのunique_ptr
一、unique_ptrはauto_の代わりに使用されますptrの.
通常、関数の実行には、次の手順があります.
1.リソースのロード
2.部分操作を行う
3.リソースの解放
次の状況を考慮します.
void fun()
{
    int *a = new int(1000);
    //do something
    delete a;
}

do something中に関数がreturnまたは関数によって実行期間前に異常を投げ出された場合、delete a;実行されず、メモリの漏洩が発生します.
この問題を解決するためにc++標準ライブラリにuniqueが導入されました.ptr.
unique_ptrは「exclusive ownership」(専属所有権)に基づいて設計され、専属所有権は同じ時間に1つのポインタが1つのオブジェクトを持つすべてのリソースを確保することです.
unique_ptrは通常のポインタと似たインタフェースを有するが,ポインタアルゴリズムがなく,例えばp++は許されない.
上の関数は、次の形式に変更できます.
void fun()
{
     unique_ptr a(new int(1000));
     //do something 
}

unique_を使用する場合ptrの場合、付与初期化は使用できません.
unique_ptrp1 = new string(“string”);//  
unique_ptrp2(new int(100));

unique_ptrはオブジェクトを持たなければならないわけではないので、空であってもよい.
std::unique_ptr<:string>p;//pはデフォルトのコンストラクタによって初期化されます
もちろん
p = nullptr;p.reset()と;同じ結果が生成され、pは空になります.
このようにして、unique_ptrにbool()が再ロードされているため、pがオブジェクトリソースを持っているかどうかを検出することができます.
if(p)
{
    ...
}

このようにして検出することもできます.
if(p == nullptr)
{
    ...
}
if(p.get() == nullptr)
{
    ...
}

release()関数unique_を取得ptrのリソースオブジェクト.
std::string *p2 = p.release();//p == nullptr

unique_ptr所有権譲渡、unique_ptrは、2つのオブジェクトが同じポインタで初期化されないことを保証するために「exclusive ownership」という意味を提供します.
std::string* sp = new std::string("hello");
std::unique_ptr<:string> up1(sp);
std::unique_ptr<:string> up2(sp);//    。 up1 and up2 own same data

これはランタイムエラーなので、論理的にこの問題を回避する必要があります.
通常の付与文やcopyコンストラクション関数を整列することはできませんが、moveの意味を使用することができます.
例:
std::unique_ptr up1(new string);
std::unique_ptr up2(up1); //  ,      
std::unique_ptr up3(std::move(up1));//ok,move  

同じ理屈:
std::unique_ptr up1(new string);
std::unique_ptr up2; // create another unique_ptr
up2 = up1; // ERROR: not possible
up2 = std::move(up1);

次のコードを見てください.
std::unique_ptr up1(new ClassA);
std::unique_ptr up2(new ClassA);
up2 = std::move(up1);

このときup 2の構造関数が呼び出されます.moveの意味を使用してup 2に値を割り当てると、up 2は元のオブジェクトの所有権を失うため、元のオブジェクトはdeleteされます.
注:unique_の場合ptrは新しいオブジェクトを割り当てます.オブジェクトはunique_でなければなりません.ptrオブジェクトは、通常のポインタではありません.
ptr = new ClassA; // ERROR
ptr = std::unique_ptr(new ClassA); // OK, delete old object

ptrにnullptrを割り当てることも可能です.
ptr = nullptr;//ptrを呼び出すのと同じである.reset();
moveの意味を使用する必要がある関数は2つあります.
1.unique_をptrは、次のようなパラメータとして関数に渡されます.
void fun(unique_ptrstr)
{
	cout << *str;
}
...
unique_ptrstr(new string("hello world"));
fun(unique_ptr(new string ("test")));
fun(std::move(str));
...

注意:fun(unique_ptrstr)関数を呼び出す場合、パラメータは必ず右の値になります.
2.関数でunique_を生成するptrを返します
unique_ptrfun()
{
	unique_ptr < string, void(*)(string*) > str(new string("1111"), [](string *str)
	{
		cout << *str;
		delete str;
	});
	return str;
}
...
auto it = fun();
...

注:この場合、C++11規格の規定に従ってコンパイラがmoveの意味を使用しようとするため、宣言move()を表示する必要はありません.
二、unique_ptrをメンバー変数とする
次の状況を考慮します.
class ClassB {
private:
	ClassA* ptr1; // pointer members
	ClassA* ptr2;
public:
	// constructor that initializes the pointers
	// - will cause resource leak if second new throws
	ClassB(int val1, int val2)
		: ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
	}
	// copy constructor
	// - might cause resource leak if second new throws
	ClassB(const ClassB& x)
		: ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
	}
	// assignment operator
	const ClassB& operator= (const ClassB& x) {
		*ptr1 = *x.ptr1;
		*ptr2 = *x.ptr2;
		return *this;
	}
	~ClassB() {
		delete ptr1;
		delete ptr2;
	}
	...
};

ptr 2の初期化時に例外が投げ出されると、ptr 1のメモリが漏洩し、この問題を回避するために、コード内の通常のポインタをunique_に変更できます.ptr.
class ClassB {
private:
std::unique_ptr ptr1; // unique_ptr members
std::unique_ptr ptr2;
public:
// constructor that initializes the unique_ptrs
// - no resource leak possible
ClassB (int val1, int val2)
: ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
}
// copy constructor
// - no resource leak possible
ClassB (const ClassB& x)
: ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
}
// assignment operator
const ClassB& operator= (const ClassB& x) {
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
// no destructor necessary
// (default destructor lets ptr1 and ptr2 delete their objects)
...
};

ここでClassAもcopyコンストラクション関数と付与操作を実装する必要があります.そうしないとmoveコンストラクション関数のみが呼び出されます.
三、操作配列
std::unique_ptr<:string> up(new std::string[10]); // runtime ERROR

でもunique_ptrとshared_ptrには違いがあります.直接宣言することができます.
std::unique_ptr<:string> up1(new std::string[10]);//ok
std::cout << *up << std::endl; // ERROR: * not defined for arrays
std::cout << up[0] << std::endl; // OK,              

次にunique_を見てみましょうptr内部での配列宣言の使用と通常のパラメータの違いについて.
namespace std {
// primary template:
template >
class unique_ptr
{
public:
...
T& operator*() const;
T* operator->() const noexcept;
...
};
// partial specialization for arrays:
template
class unique_ptr
{
public:
...
T& operator[](size_t i) const;
...
}
}

もちろん対応するのは2つのバージョンのdefault_ですdelete
namespace std {
// primary template:
template  class default_delete {
public:
void operator()(T* p) const; // calls delete p
...
};
// partial specialization for arrays:
template  class default_delete {
public:
void operator()(T* p) const; // calls delete[] p
...
};
}

ソースコードを通じて、次の2つの状況がなぜ1つ目に異常が発生したのか理解するのは難しくありません.
std::unique_ptr<:string> up1(new std::string[10]);//error
std::unique_ptr<:string> up1(new std::string[10]);//ok

四、カスタム削除器
場合によってはdefault_deleteとdefault_delete[]は私たちの要求を満たすことができません.この場合、私たちは自分の削除器を定義する必要があります.ここで定義した削除器とshared_ptrの定義方法はunique_でいくつか異なりますptrでは、削除器のタイプをテンプレートの2番目のパラメータとして宣言する必要があります.このパラメータは、関数参照、関数ポインタ、または関数オブジェクトです.
例:
class ClassADeleter
	{
	public:
		void operator () (ClassA* p) {
			std::cout << "call delete for ClassA object" << std::endl;
			delete p;
		}
	};...
std::unique_ptr up(new ClassA());

もちろん、関数またはlambda式を使用することもできますが、次のような関数のタイプを宣言する必要があります.
std::unique_ptr up(new int[10],
[](int* p) {
...
delete[] p;
});

または
auto dl = [](int* p) {
...
delete[] p;
};
std::unique_ptr> up(new int[10], dl);

もちろん、この関数のタイプを宣言する必要がないように、別名を使用して代用することもできます.
template 
	using uniquePtr = std::unique_ptr; // alias template
	...
		uniquePtr up(new int[10], [](int* p) { // used here
		...
			delete[] p;
	});

注意:
unique_ptrp1(new int);       //OK
unique_ptrp2(new int[10]);   //error           
unique_ptrp3(new int[10]); /OK
shared_ptrp4(new int);       //OK
shared_ptrp5(new int[10]);   //          
shared_ptrp6(new int[10]); //      

1.unique_ptrではタイプをvoidとして宣言できるのでvoid*も可能です
2.TがT[]と宣言された場合、*および->の代わりに[]アクションが使用されます.
3.デフォルトの削除器はdelete[]とdeleteの2種類です.
4.ポインタは互いに変換されず、親のポインタで子を指すことはできません.