《c++11学习笔记》 --- 智能指针(std::shared_ptr、std::uniq_ptr、std::weak_ptr)

目录

std::shared_ptr共享的智能指针

std::unique_ptr独占的智能指针

std::weak_ptr弱引用的智能指针

总结:


使用智能指针时需要引用头文件<memory> 

std::shared_ptr共享的智能指针

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存,在最后一个shared_ptr析构的时候,内存才会被释放。

初始化:

通过构造函数std::make_shared<T>辅助函数reset方法来初始化shared_ptr。

优先使用std::make_shared<T>来构造智能指针,提高效率。

std::shared_ptr<int> p(new int(1));

std::make_shared<int> p2(new int(1));

std::shared_ptr<int> ptr;
ptr.reset(new int(1));
if(ptr)
{
	cout<<"ptr is not  null";
}

当智能指针有值的时候,调用reset会使引用计数减1。 

获取原始指针:

 通过get()方法返回原始指针。

std::shared_ptr<int> ptr(new int(1));
int* p = ptr.get();

指定删除器:

当p的引用计数为0时,自动调用删除器deleteIntPtr来释放对象内存。或者使用lambda表达式:

void deleteIntPtr(int* p)
{
	delete p;
}
std::shared_ptr<int> p(new int, deleteIntPtr);

std::shared_ptr<int> p(new int, [](int* p)
{
		delete p;
});

 管理动态数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象。

std::shared_ptr<int> p(new int[10], [](int* p)
{
	delete[] p;
});

将std::default_delete作为删除器。default_delete的内部是通过调用delete来实现的。

std::shared_ptr<int> p(new int[10], std::default_delete<int[]>());

通过封装一个make_shared_array方法来让shared_ptr支持数组。

template<typename T>
shared_ptr<T> make_shared_array(size_t size)
{
	return shared_ptr<T>(new T[size], default_delete<T[]>());
}
std::shared_ptr<int> p = make_shared_array<int>(10);
std::shared_ptr<char> p = make_shared_array<char>(10);

注意:

1、 不要用一个原始指针初始化多个shared_ptr

int *ptr = new int;
shared_ptr<int> p(ptr);
shared_ptr<int> p2(ptr);   //逻辑错误

 2、不要在函数实参中创建shared_ptr

function (shared_ptr<int> (new int), g()); //有缺陷

c++函数参数的计算顺序在不同的编译器不同的调用约定下可能不一样。一般是从右到左,也有可能是从左到右,所以,可能的过程是先new int,然后再调用g(),如果恰好g()发生异常,而shared_ptr还没创建,则int内存泄漏。正确写法:

shared_ptr<int> p(new int());
f(p, g());

3、通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质是一个裸指针,这样可能会导致重复析构。

struct A
{
	shared_ptr<A> getSelf()
	{
		return shared_ptr<A> (this); //
    }
};
int main()
{
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->getSelf();
	return 0; 
}

 由于用同一个指针(this)构造了两个智能指针sp1和sp2,而它们之间没有任何关系。在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。

正确做法:

让目标类通过派生std::enable_shared_from_this<T>类,然后使用基类的成员函数shared_from_this来返回this的shared_ptr。

class A:public std::enable_shared_from_this<A>
{
	std::shared_ptr<A> getSelf()
	{
		return shared_from_this();
	}
};
std::shared_ptr<A> sp(new A);
std::shared_ptr<A> p = sp->getSelf();

4、避免循环引用。智能指针最大的缺陷是循环引用,循环引用会导致内存泄漏。

struct A;
struct B;

struct A
{
	std::shared_ptr<B> bptr;
	~A()
	{
		cout<<"A del"
	};
};
struct B
{
	std::shared_ptr<A> aptr;
	~B()
	{
		cout<<"B del"
	};
};
void testPtr()
{
	{
		std::shared_ptr<A> ap(new A);
		std::shared_ptr<B> bp(new B);
		ap->bptr = bp;
		bp->aptr = ap;
	}//对象应该销毁
}

两个指针A和B都不会被删除,存在内存泄漏,循环引用导致ap和bp的引用计数为2, 在离开作用域之后,ap和bp的引用计数减为1,并不会减为0,导致两个指针都不会析构,产生内存泄漏。解决办法是把A和B任何一个成员变量改为weak_ptr;

std::unique_ptr独占的智能指针

 不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。

unique_ptr<T> ptr(new T);
unique_ptr<T> ptr1 = ptr;   //错误,不能复制

 unique_ptr不允许复制,但可以通过函数返回的方式返回其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不在拥有原来指针的所有权。

unique_ptr<T> ptr(new T);
unique_ptr<T> ptr1 = std::move(ptr);

unique_ptr不像shared_ptr可以通过make_shared方法来创建智能指针,c++11目前还没有提供
make_unique方法,在c++14中会提供make_shared类似的make_unique来创建unique_ptr。
实现一个make_unique方法:

//支持普通指针
template<class T, class... Args> inline
typename enable_if<!is_arry<T>::value, unique_ptr<T>>::type
make_unique(Args&&... args)
{
	return unique_ptr<T> (new T(std::forward<Args>(args)...));
}

//支持动态数组
template<class T> inline
typename enable_if<is_arry<T>::value && extent<T>::value==0, unique_ptr<T>>::type
make_unique(size_t size)
{
	typedef typename remove_extent<T>::type U;
	return unique_ptr<T> (new U[size]());
}

//过滤掉定长数组的情况
template<class T, class... Args>
typename enable_if<extent<T>::value != 0, void>::type make_unique(Args&&... ) = delete;
/*
思路:如果不是数组,则直接创建unique_ptr。如果是数组,先判断是否为定长数组,
若为定长数组则编译不通过(因为不能这样调用make_unique<T[10]>(10),而应该这样
make_unique<T[]>(10));若为非定长数组,则获取数组中的元素类型,再根据入参size
创建动态数组的unique_ptr。
*/

unique_ptr可以指向一个数组,shared_ptr不可以。

std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 10;    //最后一个元素值为10

std::shared_ptr<int []> ptr(new int[10]);//不合法

删除器:

unique_ptr指定删除器和shared_ptr有差别:

unique_ptr指定删除器和shared_ptr有差别: 

std::shared_ptr<int> ptr(new int(1), [](int* p) {
	delete p;
});  //正确

std::unique_ptr<int> ptr(new int(1), [](int* p) {
	delete p;
});  //错误

unique_ptr指定删除器的时候需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器。可以写成:

std::unique_ptr<int, void (*)(int*)> ptr(new int(1), [](int* p) {
	delete p;
});

这种写法在lambda没有捕获变量的情况下正确,如果捕获了变量,则会编译报错:

std::unique_ptr<int, void (*)(int*)> ptr(new int(1), [&](int* p) {
	delete p;
});//错误,捕获变量

lambda在没有捕获变量的情况下是可以直接转换为函数指针的,一旦捕获了就无法转换。 如果希望unique_ptr的删除器支持lambda,可以写成:

std::unique_ptr<int, std::function<void(int*)>> ptr(new int(1), [&](int* p) {
	delete p;
});

自定义unique_ptr的删除器:

#include <memory>
#include <functional>

struct autoDelete
{
	void operator()(int*p)
	{
		delete p;
	}
};

int main()
{
	std::unique_ptr<int, autoDelete> p(new int(1)); 
	return 0;
}

std::weak_ptr弱引用的智能指针

弱引用指针weak_ptr是用来监视shared_ptr的,不会使引用计数加1,它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命周期
weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,只是作为一个旁观者来监视shared_ptr中的资源是否存在。weak_ptr还可以用来返回this指针和解决循环引用问题。

 用法:

1、通过use_count()方法获得当前观测资源的引用计数

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout<<wp.use_count()<<endl;   //输出1

2、通过expired()方法来判断所观测的资源是否已经被释放

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
{
	cout<<"weak_ptr无效,监视的智能指针已被释放"<<endl; 
}
else
{
	cout<<"weak_ptr有效"<<endl;  
}

//输出:weak_ptr有效

 3、通过lock方法来获取所监视的shared_ptr

std::weak_ptr<int> gw;
void f()
{
    if(gw.expired())  //所监视的shared_ptr是否被释放
	{
		cout<<"gw已释放 \n";
	}
	else
	{
		auto spt = gw.lock();
		cout<<*spt<<"\n";
	}
}
int main()
{
	{
		auto sp = std::make_shared<int>(42);
		gw = sp;
		f();
	}
	f();
}
//依次输出: 42     gw已释放

weak_ptr返回this指针:

在shared_ptr中:
不能直接将this指针返回为shared_ptr,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回智能指针。
原因是:std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观测this智能指针,调用shared_from_this方法时,会调用内部这个weak_ptr的lock方法,将所观测的shared_ptr返回。

class A:public std::enable_shared_from_this<A>
{
	std::shared_ptr<A> getSelf()
	{
		return shared_from_this();
	}
	~A()
	{
		cout<<"A delete"<<endl;
	}
};
	
std::shared_ptr<A> sp(new A);
std::shared_ptr<A> p = sp->getSelf();
	
//输出:A delete

/*
在外面创建A对象的智能指针和通过该对象返回this智能指针都是安全的,因为
shared_from_this()是内部的weak_ptr调用lock方法之后返回的智能指针。在
离开作用域之后,sp的引用计数减为0,A对象被析构,不会出现A对象被析构两
次的问题。
*/

注意:

获取自身智能指针的函数仅在shared_ptr<T>的构造函数被调用之后才能使用,因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。

weak_ptr解决循环引用问题:

struct A;
struct B;

struct A
{
	std::shared_ptr<B> bptr;
	~A()
	{
		cout<<"A del"
	};
};

struct B
{
	std::weak_ptr<A> aptr;
	~B()
	{
		cout<<"B del"
	};
};

void testPtr()
{
	{
		std::shared_ptr<A> ap(new A);
		std::shared_ptr<B> bp(new B);
		ap->bptr = bp;
		bp->aptr = ap;
	}//对象应该销毁
}

//输出: A del       B del

/*
在对B的成员赋值时,即执行bp->aptr = ap;时,由于aptr是weak_ptr,
它并不会增加引用计数,所以ap的引用计数仍然会是1,在离开作用域之后,
ap的引用计数会减为0,A指针会被析构,析构后其内部的bptr的引用计数会减为1,
然后在离开作用域之后bp引用计数又从1减为0,B对象也将被析构,不会造成内部泄漏。
*/

总结:

unique_ptr和shared_ptr的使用场景:要根据实际应用需求来选择,如果希望只有一个智能指针管理资源或管理数组就用unique_ptr,要是希望多个智能指针管理同一个资源就用shared_ptr

weak_ptr是shared_ptr的助手,只是监视shared_ptr管理的资源是否被释放,本身并不操作或者管理资源,用于解决shared_ptr循环引用和返回this指针的问题。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值