shared_ptr和weak_ptr

程序中需要管理各种各样的资源,为了避免内存泄露,必须确保在资源再也不需要时释放资源,智能指针提供了这一功能。C++标准提供了两大类智能指针,这里主要介绍shared_ptr。多个shared_ptr可以共享同一个资源,通过引用计数来计算资源被引用次数,并在最后一个拥有资源的shared_ptr被销毁时释放资源。

1、shared_ptr

shared_ptr定义在中,支持下列操作:

函数 作用
shared_ptr sp 默认构造函数
shared_ptr sp(ptr) 构造函数
shared_ptr sp(ptr, del) 构造函数,指定其资源和deleter
shared_ptr sp(ptr, del, ac) 构造函数,指定资源、deleter和allocator
shared_ptr sp(sp2) 复制构造函数,sp和sp2共享资源
shared_ptr sp(move(sp2)) 移动语义构造函数
shared_ptr sp(sp2, ptr) alias构造函数,和sp2共享拥有权,但指向*ptr
shared_ptr sp(wp) wp是weak_pointer
shared_ptr sp(move(up)) up是unique_ptr
shared_ptr sp(move(ap)) ap是auto_ptr
sp.~shared_ptr() 析构函数,调用deleter
sp = sp2 赋值操作,sp共享sp2的资源
sp = move(sp2) 移动语义赋值操作,sp2把拥有权交给sp
sp = move(up) up是unique_ptr
sp = move(ap) ap是auto_ptr
sp1.swap(sp2) 交换sp1和sp2的pointer和deleter
swap(sp1,sp2) 交换sp1和sp2的pointer和deleter
sp.reset() 重置shared_ptr为空
sp.reset(ptr) 重置shared_ptr,指定资源,使用默认deleter
sp_reset(ptr, del) 重置shared_ptr,指定资源和deleter
sp_reset(ptr, del, ac) 重置shared_ptr,指定资源、deleter和allocator
make_shared() 为资源构建一个shared_ptr
sp.get() 返回指向资源的指针
*sp 返回资源
sp->… 访问资源对象的成员
sp.use_count() 返回资源的引用计数
sp.unique() 判断资源是否只被引用一次
==、!=、<、>、<=、>= 判断指向资源的pointer

基本用法

shared_ptr<string> sp_book(new string("C++"));
shared_ptr<string> sp_song = new string("i want something just like this");			//error

sp_book->append(" Standard");
cout << *sp_book << endl;

定义一个管理字符串的智能指针sp_book后,就可以像普通指针一样使用sp_book,上述代码分别使用 -> 操作符访问 string 成员函数 append ,和使用 * 操作符获取字符串对象。但是注意,接受单一指针作传参的构造函数被声明为explicit,所以shared_ptr不支持指针到shared_ptr的隐式类型转换,所以对 sp_song 的赋值会提示编译错误。

make_shared

make_shared可以用来创建一个shared_ptr对象,但比构造函数更高效。因为构造函数需要申请两次内存,而make_shared只需要一次。
shared_ptr内部数据可以分为两部分:数据块和控制块。数据块就是指向资源的指针,控制块包括引用计数等数据。
shared_ptr的构造函数传参一般是动态申请内存、指向资源对象的指针,这是第一次内存分配,对应shared_ptr的数据块;构造函数内存再为其控制块申请内存,这是第二次内存分配。
make_shared是一个不定参函数模板,根据传参匹配资源对象类的构造函数(所以,make_shared的对象必须有公开的构造函数)。所以,使用make_shared不用先在外部为数据块申请内存,可以在其内部为数据块和控制块同时申请内存

class File
{
public:
	File(string filename)
	{
		std::cout << "File(string)" << std::endl;
	}

	File()
	{
		std::cout << "File()" << std::endl;
	}
};

shared_ptr<File> sp1 = make_shared<File>();				// 输出File()
shared_ptr<File> sp2 = make_shared<File>("main.cpp");	// 输出file(string)

使用shared_ptr共享数据

string *str = new string("C++");
shared_ptr<string> sp1(str);

shared_ptr<string> sp2 = sp1;		// ok
shared_ptr<string> sp3(sp1);		// ok
shared_ptr<string> sp4(str);		// error

智能指针之间共享资源时应该使用复制构造函数或者赋值操作符,它们之间共享控制块数据,也就是共享一个引用计数器;sp4的操作会产生两个引用计数器,造成资源释放两次。

enable_shared_from_this

前面提到,共享数据的智能指针是应该通过复制构造函数或赋值操作符产生的。所以,如果在被shared_ptr管理的类内部需要用到shared_ptr对象,不能直接用this指针构造一个shared_ptr,否则会导致重复释放资源对象。
正确做法是继承enable_shared_from_this类模板,调用成员函数shared_from_this。如果当前对象被shared_ptr引用,shared_from_this函数会构建一个shared_ptr对象并返回,引用计数加1;否则会抛出bad_weak_ptr异常。

class Z : public std::enable_shared_from_this<Z>
{
public:
	void output()
	{
		shared_ptr<Z> sp = shared_from_this();
		std::cout << sp.use_count() << endl;
	}

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

shared_ptr<Z> sp(new Z);
sp->output();

自定义deleter

shared_str的默认deleter会对资源指针执行delete操作来释放资源,但是对于一些特殊的资源(比如数组需要执行delete []操作,文件需要执行close操作等),默认deleter并不适用,所以需要自行定义deleter。一些构造函数和reset成员函数可以指定deleter,deleter传参可以是函数、函数对象或者lamba表达式。

void arrayDelete(int *p)
{
	cout << "arrayDelete" << endl;
	delete[] p;
}

class arrayDeleter
{
public:
	void operator () (int *p)
	{
		cout << "arrayDeleter" << endl;
		delete[] p;
	}
};

// 传函数
shared_ptr<int> sp(new int[10], arrayDelete);

// 传lambda表达式
shared_ptr<int> sp(new int[10], [](int *p) {
	delete[] p;
	cout << "arrayDeleter" << endl;
});

// 传函数对象
shared_ptr<int> sp(new int[10], arrayDeleter());

alias构造

shared_ptr有一个特殊的构造函数 shared_ptr sp(sp2, ptr),称作alias构造函数,其语义是共享控制块不共享数据块,在 sp 和 sp2 的生命周期都结束时,sp2 引用的资源对象会被释放,ptr 指向的资源不会被释放。

2、weak_ptr

weak_ptr是一种共享资源、但不拥有资源的智能指针, 所以weak_ptr不会引起资源引用计数的增减。

weak_ptr支持下列操作:

函数 作用
weak_ptr wp 默认构造函数
weak_ptr wp(sp) 基于shared_ptr构造weak_ptr对象,和shared_ptr共享资源
weak_ptr wp(wp2) 复制构造函数,和另一个weak_ptr共享资源
wp = wp2 赋值操作符, 和另一个weak_ptr共享资源
wp = sp 用shared_ptr对weak_ptr对象赋值,和shared_ptr共享资源
wp.swap(wp2) 交换两个weak_ptr的资源指针
swap(wp1, wp2) 交换两个weak_ptr的资源指针
wp.reset() 重置weak_ptr为空
wp.use_count() 返回weak_ptr拥有的资源,被shared_ptr引用的次数
wp.expired() 判断weak_ptr拥有的资源,被shared_ptr引用次数是否为0
wp.lock() 返回一个和weak_ptr共享资源的shared_ptr,会引起引用计数加1

使用weak_ptr解决shared_ptr引用成环的问题

class Node
{
public:
	shared_ptr<Node> sp_parent;
	shared_ptr<Node> sp_child;

	void setparent(shared_ptr<Node> p)
	{
		sp_parent = p;
	}

	void setchild(shared_ptr<Node> c)
	{
		sp_child = c;
	}

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	{
		shared_ptr<Node> parent(new Node());				// sp1
		shared_ptr<Node> child(new Node());					// sp2
		parent->setchild(child);
		child->setparent(parent);
	}
}

代码中各个对象的关系如下图所示,parent和child是堆上两个node对象,分别用shared_ptr sp1和sp2管理,其内部也通过sp_child和sp_parent相互引用,这种成环引用的结果是parent和child的引用计数永远不会变成0,也就不会被delete。

flowchart LR
    subgraph child
    sp_parent
    end
    subgraph parent
    sp_child
    end
    sp1 --> parent
    sp2 --> child
    sp_parent --> parent
    sp_child --> child

我们需要把sp_child和sp_parent中的至少一个改用weak_ptr来破坏成环引用。假如sp_child改用weak_ptr wp_child,那么child只被sp2拥有:
1、当sp1、sp2被销毁时,parent引用计数变为1,child的引用计数变为0;
2、child被delete,所以sp_parent被销毁,parent引用计数变为0;
3、parent被delete。

weak_ptr的使用

weak_ptr共享shared_ptr的资源,但不拥有资源,所以使用前必须确认资源是否存在:
1、使用lock()
2、使用expired(),比use_count()效率更高
3、利用构造函数从weak_ptr对象构造一个shared_ptr对象,如果资源不存在,构造函数会抛出bad_weak_ptr异常

你可能感兴趣的:(C++标准库,c++)