M.1 Intro to smart pointers and move semantics


https://www.learncpp.com/cpp-tutorial/intro-to-smart-pointers-move-semantics/
メモリの割り当ては、論理エラーによって解除されない場合があります.
これらの問題をどのように解決しますか?

Smart pointer classes to the rescue?


クラスの最大の利点の1つはdestructorが内蔵されていることです
destructorは、オブジェクトが範囲外で破棄された場合に自動的に実行されます.
したがって、コンストラクション関数にメモリを割り当てると
destructorからメモリを解放できます
ではclassを使用してpointerを管理およびクリーンアップできますか?
もちろんいい
次の例を示します.
#include <iostream>

template <typename T>
class Auto_ptr1
{
	T* m_ptr;
public:
	// Pass in a pointer to "own" via the constructor
	Auto_ptr1(T* ptr=nullptr)
		:m_ptr(ptr)
	{
	}

	// The destructor will make sure it gets deallocated
	~Auto_ptr1()
	{
		delete m_ptr;
	}

	// Overload dereference and operator-> so we can use Auto_ptr1 like m_ptr.
	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
};

// A sample class to prove the above works
class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
	Auto_ptr1<Resource> res(new Resource()); // Note the allocation of memory here

        // ... but no explicit delete needed

	// Also note that the Resource in angled braces doesn't need a * symbol, since that's supplied by the template

	return 0;
} // res goes out of scope here, and destroys the allocated Resource for us
Autoptr 1生成と同時にポインタを受信し、ポインタが消えたときに構造関数を使用してポインタを解放
その後、Resourceクラスでメッセージを生成および破棄したときに出力します.
以上のプログラムの出力は以下の通りです.
Resource acquired
Resource destroyed
Autoptr 1タイプのresはローカル変数でmainscopeを離れると消えます
したがって、Autoprt 1の構造関数は常に実行され、動的に割り当てられたメモリが解放されることを保証します.
このclassはスマートポインタと呼ばれています
インテリジェントポインタは、動的に割り当てられたメモリを管理し、インテリジェントポインタオブジェクトが範囲外の場合にメモリを削除する構成クラスです.(内蔵ポインタは自分でクリアできないため、「dumbpointer」とも呼ばれます.)

A critical flaw


Autoptr 1クラスには、自動生成コードの後ろに隠されている致命的な欠陥があります.
次の例を示します.
#include <iostream>

// Same as above
template <typename T>
class Auto_ptr1
{
	T* m_ptr;
public:
	Auto_ptr1(T* ptr=nullptr)
		:m_ptr(ptr)
	{
	}

	~Auto_ptr1()
	{
		delete m_ptr;
	}

	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
};

class Resource
{
public:
	Resource() { std::cout << "Resource acquired\n"; }
	~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
	Auto_ptr1<Resource> res1(new Resource());
	Auto_ptr1<Resource> res2(res1); // Alternatively, don't initialize res2 and then assign res2 = res1;

	return 0;
}
このプログラムの出力は以下の通りです.
Resource acquired
Resource destroyed
Resource destroyed
Autoptr 1ではcopyコンストラクション関数を定義していません.したがって、c++で提供されるdefaultコードを使用します.したがって、res 2をres 1に初期化すると、res 1,2は内蔵ポインタとして同じ値になります.
したがって,res 1,2が消失すると,同じポインタが2回解放されるため,クラッシュが発生する.
私たちは以下のような問題に直面しています.
void passByValue(Auto_ptr1<Resource> res)
{
}

int main()
{
	Auto_ptr1<Resource> res1(new Resource());
	passByValue(res1)

	return 0;
}
パラメータはpassByValueを介してcopyに渡されるため、関数内部でresがscopeから離れると破棄されます.解除されるのでmain関数の終了時にcrashが発生します
では、私たちはこの問題をどのように処理すればいいのでしょうか.
最初からcopyが作成されないように、copyコンストラクション関数と付与オペレータを明確に定義して削除することができます.これによりpassby valuecaseを防止できます.
しかし、関数でAutoptr 1を返すにはどうすればいいのでしょうか.
??? generateResource()
{
     Resource* r{ new Resource() };
     return Auto_ptr1(r);
}
Autoprt 1を参照として返すことはできません.functionの最後にAutoptr 1がdestroyされるからです.
もう1つの方法は、deepcopyを実行するためにcopy構造関数と付与オペレータを上書きすることです.この方法では、少なくとも同じオブジェクト上でポインタをコピーすることを防止することができます.しかしcopyは高価かもしれませんが、不要なobjectを作成したくないです.またdumbpointerの割り当てや初期化もobjectのコピーではありません.
どうしようかな?

Move semantics


copy constructorとassignmentオペレータを使用してポインタをコピーしない場合は、
所有権の譲渡/移動はどうなりますか?これはmove語義学に基づいた核心的な考え方である.
Move意味論(意味論)copyを作成せずに所有権を渡す
次の例を示します.
#include <iostream>

template <typename T>
class Auto_ptr2
{
	T* m_ptr;
public:
	Auto_ptr2(T* ptr=nullptr)
		:m_ptr(ptr)
	{
	}

	~Auto_ptr2()
	{
		delete m_ptr;
	}

	// A copy constructor that implements move semantics
	Auto_ptr2(Auto_ptr2& a) // note: not const
	{
		m_ptr = a.m_ptr; // transfer our dumb pointer from the source to our local object
		a.m_ptr = nullptr; // make sure the source no longer owns the pointer
	}

	// An assignment operator that implements move semantics
	Auto_ptr2& operator=(Auto_ptr2& a) // note: not const
	{
		if (&a == this)
			return *this;

		delete m_ptr; // make sure we deallocate any pointer the destination is already holding first
		m_ptr = a.m_ptr; // then transfer our dumb pointer from the source to the local object
		a.m_ptr = nullptr; // make sure the source no longer owns the pointer
		return *this;
	}

	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
	bool isNull() const { return m_ptr == nullptr;  }
};

class Resource
{
public:
	Resource() { std::cout << "Resource acquired\n"; }
	~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
	Auto_ptr2<Resource> res1(new Resource());
	Auto_ptr2<Resource> res2; // Start as nullptr

	std::cout << "res1 is " << (res1.isNull() ? "null\n" : "not null\n");
	std::cout << "res2 is " << (res2.isNull() ? "null\n" : "not null\n");

	res2 = res1; // res2 assumes ownership, res1 is set to null

	std::cout << "Ownership transferred\n";

	std::cout << "res1 is " << (res1.isNull() ? "null\n" : "not null\n");
	std::cout << "res2 is " << (res2.isNull() ? "null\n" : "not null\n");

	return 0;
}
上のclass Auto ptr 2を表示
copyコンストラクション関数では、受信オブジェクトの内部ポインタをnullptrとして指定しています.
また、割り当て演算子の場合、ポインタのメモリも解放されます.
pointerをコピーした後、nullptrにsource pointerを割り当てています.
従って、以上のプログラムの出力は以下のようになる
Resource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed

std::auto_ptr, and why it was a bad idea


今はstd::auto ptrについて話すのに最適なタイミングです.C++98から導入され、C++17から削除されたstd::auto ptrは、C++による標準化スマートポインタの最初の試みである.std::auto ptrは、auto ptr 2クラスと同じ意味移動スキームを実施することを決定する.
しかしながら、std::auto ptr(およびauto ptr 2クラス)では、使用リスクにつながる問題が多い.
まず、std::auto ptrはcopyコンストラクション関数と付与オペレータによってmoveの意味を実現するため、std::auto ptrを値として関数に渡すと、リソースはfunctionパラメータに移動され、関数の末尾で消える.(function parameterが範囲外の場合)呼び出し元がauto ptrパラメータにアクセスしようとすると(転送と削除が分からない)、空のポインタの参照が突然キャンセルされます.これは崩壊を招く
第二に、std::auto ptrは常にnon-array deleteを使用してコンテンツを削除します.これはauto ptrが動的に割り当てられた配列に対して正常に動作しないことを意味する.エラータイプのdeallocationが常に使用されるためです.動的割り当て配列の転送を防止できないため、管理が適切でないとメモリが漏洩します.
最後にauto ptrは、標準ライブラリ内の多くの他のクラス(ほとんどのコンテナとアルゴリズムを含む)とあまり調和していません.これは、標準ライブラリクラスが、アイテムをコピーするときに移動するのではなく、実際にコピーすると仮定しているためです.
上記の欠点からstd::auto ptrはC+11から削除された(使用しない).