M.8 Circular dependency issues with std::shared_ptr, and std::weak_ptr


https://www.learncpp.com/cpp-tutorial/circular-dependency-issues-with-stdshared_ptr-and-stdweak_ptr/
以前のコースではstd::shared ptrを同じリソースにする方法について議論しました.しかし、場合によっては問題になる可能性があります.
次の例を見てみましょう.
共有ポインタがそれぞれ2つのオブジェクトを指している場合.
#include <iostream>
#include <memory> // for std::shared_ptr
#include <string>

class Person
{
	std::string m_name;
	std::shared_ptr<Person> m_partner; // initially created empty

public:

	Person(const std::string &name): m_name(name)
	{
		std::cout << m_name << " created\n";
	}
	~Person()
	{
		std::cout << m_name << " destroyed\n";
	}

	friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
	{
		if (!p1 || !p2)
			return false;

		p1->m_partner = p2;
		p2->m_partner = p1;

		std::cout << p1->m_name << " is now partnered with " << p2->m_name << "\n";

		return true;
	}
};

int main()
{
	auto lucy { std::make_shared<Person>("Lucy") }; // create a Person named "Lucy"
	auto ricky { std::make_shared<Person>("Ricky") }; // create a Person named "Ricky"

	partnerUp(lucy, ricky); // Make "Lucy" point to "Ricky" and vice-versa

	return 0;
}
上記の例では、2つのPersonオブジェクトを動的に割り当てます.
次にparterUp関数を使用して2つのオブジェクトを
パートナーとしてお互いに割り当てられた共有ptr
次にプログラムを実行します.結果は次のとおりです.
Lucy created
Ricky created
Lucy is now partnered with Ricky
出力からdeallocationが発生していないことがわかります
partnerUp()関数を実行して「rickey」を指すのは2つのshared ptrです
一つはricky object,もう一つはlucy object内部のm partner
main()関数の最後にricky shared ptrがscope outになります
このときrickyはPerson objectと共有するshared ptrが存在するかどうかをチェックする
でもLucyのmパートナーが存在するのでRickyは解放されません
ここでRicky objectはLucyのmパートナによって指向される.
Lucy objectはLucyとRickyのmパートナーによって指向される
Lucy share ptrがscopeを離れると、co-ownが破棄され、チェックされます.
Lucy objectはLucyとRicky'smパートナによって指向される.
Lucy objectは構造関数をサポートしていません
プログラム終了後、「Lucy」「Ricky」というPerson class objectは解放されなかった.そのため、メモリの漏洩が発生します.
これは、共有ptrがループ参照を形成するときに発生する可能性がある.

Circular references


ループ参照とは、一連の参照が次を指し、最後の参照が最初のリング参照を指す.この場合、参照はc++のreferenceである必要はありません.
共有ptrの場合pointerは参照の意味を持つ

A reductive case


これはshared ptrが1つで起こります
#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
	std::shared_ptr<Resource> m_ptr; // initially created empty

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

int main()
{
	auto ptr1 { std::make_shared<Resource>() };

	ptr1->m_ptr = ptr1; // m_ptr is now sharing the Resource that contains it

	return 0;
}
上記の場合、m ptrはclassオブジェクト自体をshared ptrとする.
ループrefの場合、リリースできない場合があります
これにより、shared ptrが1つだけであっても、ループrefが生成される

So what is std::weak_ptr for anyway?


std::弱いprtは、このような「サイクル所有権」を解決するために設計されたスマートポインタクラスです.
std::弱ptrはobserver(光ファイバ)であり、共有ptr管理の対象または他の弱ptr観測の対象を同時に観測できる
flak ptrはownerと見なされないことを覚えておいてください
次の例では、ループリファレンスの問題を解決する方法を示します.
#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>

class Person
{
	std::string m_name;
	std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr

public:

	Person(const std::string &name): m_name(name)
	{
		std::cout << m_name << " created\n";
	}
	~Person()
	{
		std::cout << m_name << " destroyed\n";
	}

	friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
	{
		if (!p1 || !p2)
			return false;

		p1->m_partner = p2;
		p2->m_partner = p1;

		std::cout << p1->m_name << " is now partnered with " << p2->m_name << "\n";

		return true;
	}
};

int main()
{
	auto lucy { std::make_shared<Person>("Lucy") };
	auto ricky { std::make_shared<Person>("Ricky") };

	partnerUp(lucy, ricky);

	return 0;
}
以上のプログラムの出力は以下の通りです.
Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky destroyed
Lucy destroyed
予想される機能が実現したことがわかります
注目すべきはmパートナーはshared ptrではなくshared ptrである
従って、2つの共有ptrがそれぞれ破棄されるにつれて、オブジェクトは解放される
flak ptrはownerではないので、ループリファレンスの問題を解決できます

Using std::weak_ptr


std::弱いptrの欠点は直接使用できないことです
オーバーロードされたオペレータにはないので->
std::弱ptrを使用するには、まずstd::shared ptrに変換する必要があります.
例を見てみましょう
#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>

class Person
{
	std::string m_name;
	std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr

public:

	Person(const std::string &name) : m_name(name)
	{
		std::cout << m_name << " created\n";
	}
	~Person()
	{
		std::cout << m_name << " destroyed\n";
	}

	friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
	{
		if (!p1 || !p2)
			return false;

		p1->m_partner = p2;
		p2->m_partner = p1;

		std::cout << p1->m_name << " is now partnered with " << p2->m_name << "\n";

		return true;
	}

	const std::shared_ptr<Person> getPartner() const { return m_partner.lock(); } // use lock() to convert weak_ptr to shared_ptr
	const std::string& getName() const { return m_name; }
};

int main()
{
	auto lucy { std::make_shared<Person>("Lucy") };
	auto ricky { std::make_shared<Person>("Ricky") };

	partnerUp(lucy, ricky);

	auto partner = ricky->getPartner(); // get shared_ptr to Ricky's partner
	std::cout << ricky->getName() << "'s partner is: " << partner->getName() << '\n';

	return 0;
}
上記の例では、getPartner関数弱ptrを使用します.
注目すべきはmパートナーです.look()loak ptrは直接使えないので
std::弱いptrのメンバー関数.lock()を使用してshared ptrに変換して使用
パートナーがmainを離れると解除されるので、ループ依存の問題は発生しません

Conclusion


std::shared ptr複数のスマートポインタが1つのオブジェクトを共有するために使用
ループrefの問題が発生した場合、std::flak ptrを使用して問題を解決できます.
参考までにflak ptrは直接使用できません.lock()というmember関数の使用
shared ptrを使用してconvertを行うことができます