C++智能指针(一)——shared_ptr

一、为什么有智能指针

智能指针的出现是为了解决:

C++没有内存回收机制,每次程序员new出来的对象需要手动delete,流程复杂时可能会漏掉delete,导致内存泄漏。于是C++引入智能指针,可用于动态资源管理,资源即对象的管理策略。使用 raw pointer 管理动态内存时,经常会遇到这样的问题:

        忘记delete内存,造成内存泄露。

        出现异常时,不会执行delete,造成内存泄露。

下面用一个小例子来说明其实现的原理。

#include <iostream>
#include <memory>

struct C { int* data; };

int main() 
{
	std::shared_ptr<int> p1;//默认构造函数,refcount为0
	std::shared_ptr<int> p2(nullptr);//使用一个空的对象初始化时refcount也为0
	//普通指针初始化是引用计数为1,p3,p4
	std::shared_ptr<int> p3(new int);
	std::shared_ptr<int> p4(new int, std::default_delete<int>());

	//拥有allocator的时候初始化同样引用计数为1
	//但是紧接着又用改智能指针拷贝构造初始化其他智能指针p6,
	//所以最后其引用计数为2,p5
    std::shared_ptr<int> p5(new int, [](int* p) {delete p; }, std::allocator<int>());

	//这里p6本身为1,但是因为使用std::move去初始化p7,将p6指向转给了p7
	//则p6智能指针recount--变为0,p7 ++由1变为2
	std::shared_ptr<int> p6(p5);
	std::shared_ptr<int> p7(std::move(p6));
	std::shared_ptr<int> p8(std::unique_ptr<int>(new int));
	std::shared_ptr<C> obj(new C);
	std::shared_ptr<int> p9(obj, obj->data);

	std::cout << "use_count:\n";
	std::cout << "p1: " << p1.use_count() << '\n';
	std::cout << "p2: " << p2.use_count() << '\n';
	std::cout << "p3: " << p3.use_count() << '\n';
	std::cout << "p4: " << p4.use_count() << '\n';
	std::cout << "p5: " << p5.use_count() << '\n';
	std::cout << "p6: " << p6.use_count() << '\n';
	std::cout << "p7: " << p7.use_count() << '\n';
	std::cout << "p8: " << p8.use_count() << '\n';
	std::cout << "p9: " << p9.use_count() << '\n';
	return 0;
}

        1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
        2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。

二、shared_ptr的应用

2.1应用与容器

     将shared_ptr作为容器的元素,如vector<shared_ptr<T>>,因为shared_ptr支持拷贝语义和比较操作,符合标准容器对于元素的要求,所以可以实现在容器中安全的纳元素的指针而不是拷贝。

#include <boost/make_shared.hpp>
#include <vector>
#include<string>
#include <boost/smart_ptr.hpp>
#include <boost/make_shared.hpp>
#include<stdio.h>
#include<iostream>

using namespace boost;


int main()
{
	
	typedef std::vector<shared_ptr<int>> vs;
	vs v(10);
	int i = 0;
	for (vs::iterator pos = v.begin(); pos != v.end(); ++pos)
	{
		(*pos) = make_shared<int>(++i);	//使用工厂函数赋值
		using namespace std;
		cout << *(*pos) << ",";	//输出值
	}
	using namespace std;
	cout << endl;
	
	boost::shared_ptr<int> p = v[9];
	*p = 100;
	cout << *v[9] << endl;
}

  2.2应用于桥接模式

        桥接模式(bridge)是一种结构型的设计模型,它把类的具体的实现细节对用户隐藏起来,已到达类之间的最小 耦合,类内高度内聚(高内聚低耦合),在具体的实现中桥接模式也被称为pimpl或者handle/body惯用法,它可以将 头文件的 依赖关系讲到最低,减少编译时间,而且可以比使用虚函数实现多态通过一个小例子来说明share_ptr如何用于桥接模式

class sample
	{
	private:
		class impl;			//不完整的内部类声明
		shared_ptr<impl> p;		//shared_ptr的成员变量
	public:
		sample();	//构造函数
		void print();	//提供给外界的接口
	};
	//在sample中完整定义impl类和其他功能
	class sample::impl
	{
	public:
		void print()
		{
			using namespace std;
			cout << "impl print" << endl;
		}
	};
	sample::sample() :p(new impl) {}	//构造函数初始化shared_ptr

	void sample::print()				//调用pimpl实现print()
	{
		p->print();
	}
	//最后是使用桥接模式
	int main()
	{
	sample s;
	s.print();
	}

        桥接模式非常有用,它可以任意改变具体的实现外而界对此一无所知,也减少了文件之间的编译依赖,是程序获得更多的灵活性

2.3应用于工厂模式

       工厂模式是一种创建型设计模式,这个模式包装了new操作符的使用,使对象的创建工作集中在工厂类或者工厂函数中,从而更容易适应变化,make_shared()就是工厂模式的一个很好的例子
但由于C++不能高效的返回一个对象,在程序中编写自己的工厂类或者工厂函数中时通常需要早堆区使用new的方法动态的分配一个对象,然后返回指针。这种做法很不安全,因为用户很容易忘记对指针调用delete,存在资源泄露的隐患。所以用shared_ptr来解决这个问题


class abstract					//接口类的定义
{
public:
	virtual void f() = 0;
	virtual void g() = 0;
protected:
	virtual ~abstract() {};		//注意这里
};
/*注意abstract的析构函数,被定义为保护的,意味着除了它自己和他的子类,其他的对象无权来调用
delete来删除它。*/
class impl :public abstract
{
public:
	virtual void f()
	{
		using namespace std;
		cout << "class impl f" << endl;
	}
	virtual void g()
	{
		using namespace std;
		cout << "class impl g" << endl;
	}
};
//随后工厂函数返回基类的shared_ptr
shared_ptr<abstract>creat()
{
	return shared_ptr<abstract>(new impl);
}
//这样就完成全部的工厂模式的实现,现在可以把这些组合起来
int main()
{
	shared_ptr<abstract> p = creat();//工厂函数创建对象
	p->f();			//可以像普通指针一样使用
	p->g();			//不必担心资源泄露,shared_ptr会自动管理指针
}

三、高级议题


        shared_ptr<void> 能够存储void* 型的指针,而void* 型的指针可以指向任意类型,因此她就像一个泛型指针容器,拥有容纳任意类型的能力,但同时也会丧失原来的类型信息,不建议这样使用shared_ptr的功能已经远远超出了智能指针的范围,限于篇幅就不做介绍了;有兴趣自己可以百度。

四、shared_array的用法

shared_array和shared_ptr的接口功能几乎时相同的,主要区别如下:
    构造函数接受的P指针必修是new[]的结果,而不能是new表达式的结果
    提供operator【】操作符重载,可以像普通数组一样用下标访问元素
    没有*、->操作符的重载,因为shared_array持有的不是一个普通指针
    析构函数用的是delete【】而不是delete。

4.1用法

      shared_array就像是shared_ptr和scoped_array的结合体哟杨具有shared_ptr的优点,也有scoped_array的缺点;

#include <boost/make_shared.hpp>
#include <vector>
#include<string>
#include <boost/smart_ptr.hpp>
#include <boost/make_shared.hpp>
#include<stdio.h>
#include<iostream>
using namespace boost;
int main()
{
	int *p = new int[100];		//一个动态数组
	shared_array<int> sa(p);	//shared_array代理的动态数组
	shared_array<int> sa2 = sa;//共享数组,引用技术增加

	sa[0] = 10;	//可以使用operator访问元素
	assert(sa2[0] == 10);
}			

       同样的,在使用shared_array重载的operator【】时要小心,shared_array不提供数组索引的范围检查,如果使用了超过动态数组大小的索引或者时负数索引将引发可怕的未定义行为;一般情况下使用shared_ptr<std::vector> 或者std::vector<shared_ptr>来代替.

后续继续更新相关内容

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小气鬼944

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值