欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++11中weak_ptr的使用

程序员文章站 2022-07-02 15:52:08
在c++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与...

在c++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的。有时会忘记释放内存,在这种情况下会产生内存泄露;有时在尚有指针引用内存的情况下就释放了它,在这种情况下就会产生引用非法内存的指针。

为了更容易(同时也更安全)地使用动态内存,c++11标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指的对象。c++11标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象。c++11标准库还定义了一个名为weak_ptr的辅助类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。智能指针是模板类而不是指针。类似vector,智能指针也是模板,当创建一个智能指针时,必须提供额外的信息即指针可以指向的类型。默认初始化的智能指针中保存着一个空指针。智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。

pointer misuse can be a major source of bugs. smart pointers prevent most situations of memory leaks by making the memory deallocation automatic. more generally, they make object destruction automatic: an object controlled by a smart pointer is automatically destroyed (finalized and then deallocated) when the last (or only) owner of an object is destroyed.

in c++, a smart pointer is implemented as a template class that mimics, by means of operator overloading, the behaviors of a traditional (raw) pointer, while providing additional memory management features.

std::weak_ptr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr. it must be converted to std::shared_ptr in order to access the referenced object.

std::weak_ptr models temporary ownership: when an object needs to be accessed only if it exists, and it may be deleted at any time by someone else, std::weak_ptr is used to track the object, and it is converted to std::shared_ptr to assume temporary ownership. if the original std::shared_ptr is destroyed at this time,the object's lifetime is extended until the temporary std::shared_ptr is destroyed as well.

in addition,std::weak_ptr is used to break circular references of std::shared_ptr.

weak_ptr is primarily useful in the rare cases where it is needed in order to break'circular references' – a problem with the use of reference counting in shared_ptr.

sometimes an object must store a way to access the underlying object of a shared_ptr without causing the reference count to be incremented. typically, this situation occurs when you have cyclic references between shared_ptr instances. the best design is to avoid shared ownership of pointers whenever you can. however, if you must have shared ownership of shared_ptr instances, avoid cyclic references between them. when cyclic references are unavoidable, or even preferable for some reason, use weak_ptr to give one or more of the owners a weak reference to another shared_ptr. by using a weak_ptr, you can create a shared_ptr that joins to an existing set of related instances, but only if the underlying memory resource is still valid. a weak_ptr itself does not participate in the reference counting, and therefore, it cannot prevent the reference count from going to zero. however, you can use a weak_ptr to try to obtain a new copy of the shared_ptr with which it was initialized. if the memory has already been deleted, a bad_weak_ptr exception is thrown. if the memory is still valid, the new shared pointer increments the reference count and guarantees that the memory will be valid as long as the shared_ptr variable stays in scope.

weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。同样,在weak_ptr析构时也不会导致引用计数的减少,它只是一个静静地观察者。weak_ptr没有重载operator*和->,这是特意的,因为它不共享指针,不能操作资源,这是它弱的原因。但它可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。

weak_ptr用于解决”引用计数”模型循环依赖问题,weak_ptr指向一个对象,并不增减该对象的引用计数器。weak_ptr用于配合shared_ptr使用,并不影响动态对象的生命周期,即其存在与否并不影响对象的引用计数器。weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。weak_ptr提供了expired()与lock()成员函数,前者用于判断weak_ptr指向的对象是否已被销毁,后者返回其所指对象的shared_ptr智能指针(对象销毁时返回”空”shared_ptr)。

智能指针是模板类而不是指针.

weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shard_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。

当创建一个weak_ptr时,要用一个shared_ptr来初始化它。不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr。与任何其它shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。

下图列出了weak_ptr支持的操作(来源于c++ primer fifth edition 中文版):

C++11中weak_ptr的使用

 

下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

#include "weak_ptr.hpp"
#include 
#include 
#include 
#include 
#include 

///////////////////////////////////////////////////////
// reference: https://en.cppreference.com/w/cpp/memory/weak_ptr
std::weak_ptr gw;

void f()
{
	if (auto spt = gw.lock()) { // has to be copied into a shared_ptr before usage
		std::cout << *spt << "\n";
	}
	else {
		std::cout << "gw is expired\n";
	}
}

int test_weak_ptr1()
{
	{
		auto sp = std::make_shared(42);
		gw = sp;

		f();
	}

	f();

	return 0;
}

/////////////////////////////////////////////////
// reference: https://*.com/questions/12030650/when-is-stdweak-ptr-useful
int test_weak_ptr2()
{
	// old, problem with dangling pointer
	// problem: ref will point to undefined data!
	int* ptr = new int(10);
	int* ref = ptr;
	delete ptr;

	// new
	// solution: check expired() or lock() to determine if pointer is valid
	// empty definition
	std::shared_ptr sptr;

	// takes ownership of pointer
	sptr.reset(new int);
	*sptr = 10;

	// get pointer to data without taking ownership
	std::weak_ptr weak1 = sptr;

	// deletes managed object, acquires new pointer
	sptr.reset(new int);
	*sptr = 5;

	// get pointer to new data without taking ownership
	std::weak_ptr weak2 = sptr;

	// weak1 is expired!

	if (auto tmp = weak1.lock())
		std::cout << *tmp << '\n';
	else
		std::cout << "weak1 is expired\n";

	// weak2 points to new data (5)

	if (auto tmp = weak2.lock())
		std::cout << *tmp << '\n';
	else
		std::cout << "weak2 is expired\n";

	return 0;
}

//////////////////////////////////////////////////////
// reference: https://msdn.microsoft.com/en-us/library/hh279672.aspx
class controller
{
public:
	int num;
	std::string status;
	std::vector> others;
	explicit controller(int i) : num(i), status("on")
	{
		std::cout << "creating controller" << num << std::endl;
	}

	~controller()
	{
		std::cout << "destroying controller" << num << std::endl;
	}

	// demonstrates how to test whether the pointed-to memory still exists or not.
	void checkstatuses() const
	{
		for_each(others.begin(), others.end(), [](std::weak_ptr wp) {
			try {
				auto p = wp.lock();
				std::cout << "status of " << p->num << " = " << p->status << std::endl;
			}
			catch (std::bad_weak_ptr b) {
				std::cout << "null object" << std::endl;
			}
		});
	}
};

void runtest()
{
	std::vector> v;

	v.push_back(std::shared_ptr(new controller(0)));
	v.push_back(std::shared_ptr(new controller(1)));
	v.push_back(std::shared_ptr(new controller(2)));
	v.push_back(std::shared_ptr(new controller(3)));
	v.push_back(std::shared_ptr(new controller(4)));

	// each controller depends on all others not being deleted.
	// give each controller a pointer to all the others. 
	for (int i = 0; i < v.size(); ++i) {
		for_each(v.begin(), v.end(), [v, i](std::shared_ptr p) {
			if (p->num != i) {
				v[i]->others.push_back(std::weak_ptr(p));
				std::cout << "push_back to v[" << i << "]: " << p->num << std::endl;
			}
		});
	}

	for_each(v.begin(), v.end(), [](std::shared_ptr& p) {
		std::cout << "use_count = " << p.use_count() << std::endl;
		p->checkstatuses();
	});
}

int test_weak_ptr3()
{
	runtest();
	std::cout << "press any key" << std::endl;
	char ch;
	std::cin.getline(&ch, 1);

	return 0;
}

////////////////////////////////////////////////
// reference: https://oopscenities.net/2014/08/03/c-smart-pointers-part-5-weak_ptr/
struct child;
struct parent
{
	std::shared_ptr child;

	~parent() { std::cout << "bye parent" << std::endl; }

	void hi() const { std::cout << "hello" << std::endl; }
};

struct child
{
	std::weak_ptr parent;
	//std::shared_ptr parent; // memory leak

	~child() { std::cout << "bye child" << std::endl; }
};

int test_weak_ptr4()
{
	auto parent = std::make_shared();
	auto child = std::make_shared();

	parent->child = child;
	child->parent = parent;
	child->parent.lock()->hi();
	// child->parent->hi();

	return 0;
}

/////////////////////////////////////////////////////
// reference: https://thispointer.com/shared_ptr-binary-trees-and-the-problem-of-cyclic-references/
class node
{
	int value;
public:
	std::shared_ptr leftptr;
	std::shared_ptr rightptr;
	// just changed the shared_ptr to weak_ptr
	std::weak_ptr parentptr;
	node(int val) : value(val)     {
		std::cout << "contructor" << std::endl;
	}
	~node()     {
		std::cout << "destructor" << std::endl;
	}
};

int test_weak_ptr5()
{
	std::shared_ptr ptr = std::make_shared(4);
	ptr->leftptr = std::make_shared(2);
	ptr->leftptr->parentptr = ptr;
	ptr->rightptr = std::make_shared(5);
	ptr->rightptr->parentptr = ptr;
	std::cout << "ptr reference count = " << ptr.use_count() << std::endl;
	std::cout << "ptr->leftptr reference count = " << ptr->leftptr.use_count() << std::endl;
	std::cout << "ptr->rightptr reference count = " << ptr->rightptr.use_count() << std::endl;
	std::cout << "ptr->rightptr->parentptr reference count = " << ptr->rightptr->parentptr.lock().use_count() << std::endl;
	std::cout << "ptr->leftptr->parentptr reference count = " << ptr->leftptr->parentptr.lock().use_count() << std::endl;

	return 0;
}

githubhttps://github.com/fengbingchun/messy_test