C++智能指针【shared_ptr】基本使用

一、概述

C++ 标准模板库 STL(Standard Template Library) 一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr。虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用更加安全的 unique_ptr,后文会详细叙述。shared_ptr 和 weak_ptr 则是 C+11 从准标准库 Boost 中引入的两种智能指针。此外,Boost 库还提出了 boost::scoped_ptr、boost::scoped_array、boost::intrusive_ptr 等智能指针,虽然尚未得到 C++ 标准采纳,但是在开发实践中可以使用。

二、智能指针优点

在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

三、shared_ptr

1. 格式

shared_ptr<指向的类型> 智能指针名;

2. 智能指针初始化

注意:
1. 智能指针是explicit,不可以进行隐式类型转换。必须直接初始化形参。
2.裸指针可以初始化shared_ptr,但不推荐。智能指针和裸指针不要穿插使用。

shared_ptr<int> p(new int(100));
shared_ptr<int> q = new int(20);	//编译报错,智能指针是explicit,不可以进行隐式类型转换。必须直接初始化形参
//裸指针可以初始化shared_ptr,但不推荐
int *p = new int;
shared_ptr<int> q(p);

3. make_shared函数

标准库中的函数模板,安全,高效的分配和使用shared_ptr。

动态内存中分配并初始化一个对象,然后返回指向此对象的shared_ptr

shared_ptr<int> p = make_shared<int>(100);
shared_ptr<string> str = make_shared<string>(5, 'a');	//5个字符a生成的字符串
shared_ptr<int> q = make_shared<int>();	//q指向int,内存里面保存的值为0
q = make_shared<int>(250);	//q先释放原内存,然后指向一个新int,内存里面保存的值为250

auto p2 = make_shared<int>(22);	//用 auto类型推断

4. shared_ptr引用计数

每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象

1. 引用计数加增加(+1)

  • 用一个智能指针来初始化另一个智能指针。
  • 把智能指针以值的形式当做实参往函数里传递。(以引用的方式传递则不会增加)
  • 作为函数返回值。
//如果参数为引用,则智能指针的引用计数不会增加
shared_ptr<int> Func(const shared_ptr<int> &p)	{return p;}

int main(){
	auto p = make_shared<int>(100);
	auto q = Func(p);

	return 0;
}

2. 引用计数减少(-1)

  • 给shared_ptr赋予新值,让该shared_ptr指向一个新对象。
  • 局部的shared_ptr 离开作用域。
  • 当shared_ptr引用计数从1变成0,则会自动释放自己所管理(指向)的对象。
//如果参数为引用,则智能指针的引用计数不会增加
void Func(const shared_ptr<int> p)	{return ;}
int main()
{
	auto p = make_shared<int>(100);
	p = make_shared<int>(5);
	Func(p);

	return 0;
}

5. shared_ptr常用操作

  • use_count() :返回多少个智能指针指向某个对象,主要用于调试。
shared_ptr<int> p(new int(100));
cout << p.use_count() << endl; 	//1
  • unique():是否独占某个指向的对象,也就是只有一个智能指针指向某个对象,则返回true,否则返回false
shared_ptr<int> p(new int(100));
cout << p.unique() << endl;		//true;
  • reset() : 包含两个操作。当智能指针中有值的时候,调用reset()会使引用计数减1.当调用reset(new xxx())重新赋值时,智能指针首先是生成新对象,然后将就对象的引用计数减1(当然,如果发现引用计数为0时,则析构旧对象),然后将新对象的指针交给智能指针保管。
shared_ptr<int> p(new int(100));
p.reset(new int(50));
  • 星号解引用 : 获得智能指针指向的对象。
struct A	
{
	int a = 100;	//类内初始化
	string name = "三种";
};

int main()
{
	shared_ptr<A> p(new A);
	cout << (*p).a << endl;	//100
	
	//shared_ptr p(new int(100));
	//cout << *p << endl;	//100

	return 0;
}
  • get():返回智能指针中保存的裸指针(普通指针),小心使用,如果智能指针释放了所指向的对象,那么这个返回的裸指针也就变得无效
    考虑到有些函数的参数需要的是一个内置裸指针而不是智能指针。
shared_ptr<int> p(new int(100));
int* q = p.get();
*q = 45;
//delete q;	//释放内存会导致程序崩溃
  • swap():交换两个智能指针所指向的对象
shared_ptr<string> p(new string("张飞"));
shared_ptr<string> q(new string("I Love China"));
p.swap(q);

cout << *p << endl;
  • = nullptr:将所指向的对象引用计数减1,若引用计数变为0,则释放智能指针所指向的对象,将智能指针置空
shared_ptr<string> p(new string("张飞"));
p = nullptr;
  • 智能指针名字作为判断条件
shared_ptr<string> p(new string("张飞"));
if(p){cout << "" << endl;}
  • 指定删除器以及数组的问题
    一定时机帮我们删除所指向的对象,我们可以指定自己的删除器来取代系统提供的默认删除器,当智能指针需要删除所指向的对象时,编译器就会调用我们自己的删除器。
    自定义删除器,删除整型指针用的, 当智能指针引用计数为0,就会自动调用该删除器来删除对象
//自定义删除器
void Func(int *p)	
{
	//写一些日志
	delete p;
}
int main()
{
	shared_ptr<int> p(new int(12), Func);
	shared_ptr<int> q(p);
	q.reset();	//引用计数减1 ,q为nullptr;
	p.reset();	//会调用自定义删除器

	//删除器可以是一个lambda表达式
	shared_ptr<int> m(new int(10), [](int* p) {delete p; });
	
	return 0;
}

有些情况,默认删除器处理不了(用shared_ptr管理动态数组),需要我们自己指定删除器。

shared_ptr<int> p(new int[10], [](int *p){delete []p;});	
shared_ptr<int[]> q(new int[10]);	//此种写法不需要自定义删除器
class A
{
public:
	A() {}
	~A() {};

};
int main()
{
	//shared_ptr p(new A[10]);		//不自定定义删除器会报异常
	shared_ptr<A> q(new A[10], [](A* q) {delete[]q; });	//需要写自己的删除器
	//可以用default_delete来做删除器,default_delete是标准库里面的模板类
	shared_ptr<A> p(new A[10], std::default_delete<A[]>());
	//定义数组的时候在<>中加[]
	shared_ptr<A[]> p(new A[10]);
	return 0;
}

写个函数模板来封装shared_ptr数组

//写个函数模板来封装shared_ptr数组
template<typename T>
shared_ptr<T> Func(size_t size)
{
	return shared_ptr<T>(new T[size], default_delete<T[]>());
}
int main()
{
	shared_ptr<int> q = Func<int>(5);
	return 0;
}

注意:两个shared_ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared_ptr也属于同一个类型

6. 使用shared_ptr需要注意的问题

但凡一些高级的用法,使用时都有不少陷阱。

  1. 不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁,如下所示:
int *p5 = new int;
std::shared_ptr<int> p6(p5);
std::shared_ptr<int> p7(p5);// logic error
  1. 慎用get()返回的指针;返回智能指针指向的对象所对应的裸指针(原始指针,有些接口函数可能只能使用裸指针),get()返回的指针不能用delete,否则会异常
shared_ptr<int> p(new int(100));
int* q = p.get();
delete q;	//不可以删除,会导致异常
  1. 不要在函数实参中创建shared_ptr。因为C++的函数参数的计算顺序在不同的编译器下是不同的。正确的做法是先创建好,然后再传入。
function(shared_ptr<int>(new int), g());
  1. 不要把类对象指针(this)作为shared_ptr返回,这样做可能也会造成二次析构。要通过shared_from_this()返回this指针
//用到C++标准库里面的类模板:enable_shared_from_this;
//enable_shared_from_this中有一个弱指针weak_ptr,这个弱指针能够监视this,实际内部调用weak_ptr的lock()方法
class Test: public enable_shared_from_this<Test>
{
public:
	shared_ptr<Test> Func() {
		return shared_from_this();
	}
};

int main()
{
	shared_ptr<Test> p(new Test);
	shared_ptr<Test> q = p->Func();	//返回this智能指针
	//现在在外面创建Test对象的智能指针以及通过Test对象返回的this智能指针都是安全的

	return 0;
}

weak_ptr使用可参考博客:C++智能指针【weak_ptr】基本使用

  1. 避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。解决方法是AStruct或BStruct改为weak_ptr。
class B;
class A
{
public:
	shared_ptr<B> b;
	~A()
	{
		int a = 0;
	}
};

class B
{
public:
	shared_ptr<A> a;
	~B()
	{
		int b = 0;
	}
};

int main()
{
	shared_ptr<A> a(new A);
	shared_ptr<B> b(new B);
	
	a->b = b;
	b->a = a;

	//两个析构函数都没有被执行,将A或B类里面的智能指针改成weak_ptr即可
	return 0;
}

你可能感兴趣的:(C++基础/高级,指针,c++,内存管理)