C++ 11 _智能指针

C++ 11 移除了auto_ptr然后重新引用了shared_ptr,unique_prt,weak_ptr 等智能指针

1 shared_ptr共享智能指针(头文件memory)

1.1 关于shared_ptr

std::share_ptr 使用引用计数来让多个对象之间共享一块内存,只有当最后一个变量析构的时候,也就是引用计数为0的时候 才会释放内存,所以内存的释放,就是通过引用计数来判断的,share_ptr在包含了两部分,一部分是裸指针raw_ptr 用来指向堆上申请的地址,一个指向内部隐藏的,管理的对象share_count_object

1.2 初始化share_ptr
//第一种方式 直接调用对象的构造函数
std::shared_ptr<int> p1(new int(1));

//第二种方式,先声明对象然后调用reset初始化
std::shared_ptr<int>p2;
p2.reset(new int(1));

//第三种方式 也是推荐的方式以及最高效的方式 使用make_shared
auto ptr = make_shared<int>(100); //auto 也是C++ 11 的新特性 
//这种初始化方式等价与 std::shareptr(new int(100));

ps : 1.这里分析下为什么使用make_shared的创建方式会更加的高效了,在我们是使用 方式1的去初始化一个shared_ptr的时候,我们需要先在堆上申请分配一块内存,用来存储数字1,然后用这个变量调用share_ptr的构造函数,再次分配一次内存,而使用make_shared就只需要分配一次内存就可以了
ps 2. 关于智能指针调用reset的初始化方式,这个函数在智能指针没有值的时候调用是用来初始化的,当这个智能指针有值的时候,调用reset函数就会引起智能指针引用计数-1

1.3 share_ptr 的一些操作成员函数

A: reset 没有值的时候用来初始化指针,有值的时候会让引用计数-1

std::share_ptr<int> p1;
p1.reset(new int(10)); //p1未指向任何地址,reset初始化

std::share_ptr<int> p2;
p2 = p1; //p2 与p1 都指向同一块内存地址 引用计数+1
cout << p2.use_count <<endl; // 2  当前有两个share_ptr 强引用这块地址
p1.reset(); //引用计数-1
cout << p2.use_count <<endl;// 1

B: use_count 获取当前的引用计数的数量 代码见上面

C: get 获取原始指针 不要用

auto ptr = make_shared<int>(100);
int *p = ptr.get(); //获取share_ptr的原始指针 也就是上面讲到的第一部分用来存储真正数据的地方
  • 一定要谨慎的使用get 函数,当我们用一个裸指针 上面的(int*) 来保存地址的时候,我们没有办法掌握ptr会在什么时候释放这块内存地址,这样我们在使用的过程中就会产生不可预知的错误
  • 如果我们获取了这个裸指针,一不小心调用了delete 就会导致同一块内存地址析构了两次
  • 如果我们用一个shared_ptr来保存这个地址,那么我们就相当于又有了一个从1开始计数的shared_ptr与ptr都指向这块内存,最终的结果就是调用两次析构

D: 指定删除器 如果shared_ptr管理的不是new的对象,或者类没有析构函数的时候 应该指定删除器

#include
#include
using namespace std;
void DeleteSharePtr(int *p)
{
	cout << "Delete Share Ptr" << endl;
	delete p;
}
int main()
{
	//生成的时候传入删除器
	std::shared_ptr<int>p1(new int(100),DeleteSharePtr);
	//也可以是使用lambda表达式来弄
	std::shared_ptr<int>p2(new int(120), [](int*p){
		cout << "Lambda delete ptr" <<endl;
		delete p;
	})
	std::shared_ptr<int>p3(new int[10],[](int *p){
		delete []p;
	})
}

PS :当我们用shared_ptr管理数组的时候,一定要指定删除器

1.4 shared_ptr智能指针的使用注意事项

A: shared_ptr 不能通过直接赋值来初始化,只能通过它的构造函数或者辅助函数来初始化

std::shared_ptr<int> p1 = new int(10); //方法错误 不能通过这种方式进行初始化

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

int * ptr = new int(10);
std::shared_ptr<int> p1(ptr); //用ptr初始化share_ptr p1 指向这块内存
std::shared_ptr<int> p2(ptr);//用ptr初始化share_ptr p2指向这块内存
// 最后的结果就是ptr所指向的内存 被析构了两次

C: * 不要在函数*实参中创建share_ptrfunction(shared_ptr (new int), g()) 因为在不同的C++编译器的计算顺序是不一样的,一般都是从右到左,但也有从左到右的过程,如果我们先new int ,然后在调用g()的时候异常了,那么我们申请的内存就会没有释放,而此时shared_ptr还没有创建,则存在了内存泄漏的风险,这也是推荐使用make_shared得原因

D:通过指定的接口返回this 指针,因为将this指针作为shared_ptr对象返回,会导致重复析构,与B的情况类似,而我们返回的时候需要让目标通过继承std::enable_shared_from_this然后通过基类的接口shared_from_this()来返回

#icnlude<iostream>
#include
using namespace std;
class A
{
	public:
		shared_ptr<A>GetSelf()
		{
			return shared_ptr<A>(this);
		}
		~A()
		{
			cout << "Deconstrution A" <<endl;	
		}
}
class B:public std::enable_shared_from_this<A>
{
	public :
	std::shared_ptr<B> GetSelf()
	{
		return shared_from_this();
	}
	~B
	{
		cout << "Deconstrution B" <<endl;	
	}
}
int main()
{
	std::shared_ptr<A> p1(new A);
	std::shared_ptr<A>p2 = p1->GetSelf(); // 错误 将会导致两次析构
	auto p3 = make_shared<B>();
	auto p4 = p3.GetSelf(); //正确
}

E:* 避免循环引用导致内存泄漏,当我们在两个不同的类里都互相存着对方的成员子对象share_ptr的时候,我们就会发现,这两个类创造出来的对象都存在循环引用的场景,导致内存泄漏 *

#include
#include
using namespace std;
class A;
class B;
class A
{
	public:
	std::shared_ptr<B> pb;
	~A(){cout << "A Destroy" <<endl;}
};
class B
{
public :
	std::shared_ptr<A>pa;
	~B(){cout <<"B Destroy"<<endl;}
};
int main()
{
	{
		auto pa = make_shared<A>();
		auto pb = make_shared<B>();
		pa->pb = pb;
		pb->pa = pa;
		cout<< pa.use_count ()<<endl; //2
        cout<<pb.use_count ()<<endl; //2
	}
	//在作用域结束的时候我们会发现,根本就没有调用一次析构函数,因为直到作用域结束 两个指针互相引用导致引用计数不能为0,而不调用析构函数
	cout << "Main End Return" <<endl;
	return 0;
}

2 unique_ptr 独占的智能指针

2.1 unique_ptr 相关的知识

Unique_ptr 独占的智能指针,这个指针与shared_ptr 不同,就是在某个时候一定只有一个unique_ptr指向一个特定的对象,,当unique_prt被销毁的时候它所指向的对象也会被销毁

2.2 unique_ptr的初始化

与shared_ptr不同的是,在C++11中没有make_shared这样的函数用来初始化,在C++14中又了make_unique()函数用来初始化一个unique_ptr,而在C11中只能通过直接初始化的形式,由于一个unique_ptr 只存在一个拥有指向的对象,所以它不支持普通的拷贝和操作赋值

#include
unique_ptr<string> p1(new string("12300")); //直接初始化形式
// unique_ptrp2(p1) //unique_ptr(const unique_ptr&) = delete;
//unique_ptrp3;
//p3 = p1  //unique_ptr& operator=(const unique_ptr&) = delete;
unique_ptr<T,func>p2(new string("A"), func) //指定删除器的初始化的形式

2.3 unique_ptr的一些基本特性

A:*unique_ptr虽然不能拷贝和赋值,但是可以通过函数返回给其他的unique_ptr 还可以通过std::move 来转移到其他的unique_ptr 这样它本身就不再拥有原来指针的所有权了 在这里不用感到奇怪就是为什么函数的返回值能够赋值给另外的unique_ptr 而直接调用的却不行了,这是因为直接赋值的unique_ptr& operator=(const unique_ptr&) = delete 这个函数是不能使用的,而函数的返回值 是一个右值,而unique_ptr是实现了右值版本的赋值函数的

#include
unique_ptr<int> my_ptr(new int(1));
unique_ptr<int>p2 = std::move(my_ptr);

B:创建数组形变量的时候与shared_ptr 的不同,unique_ptr是可以指定数组大小的,而shared_ptr是不可以指定的 因为shared_ptr创建数组类型变量时候需要指定删除器

#include
unique_ptr<int []>p1(new int[10]);
shared_ptr<int []>p2(new int[10]); //错误

C:指定删除器与shared_ptr的不同,unique_ptr指定删除器的时候,需要确定删除器的类型

std::shared_ptr<int>(new int(1),[](int*p){
	delete p;
})
//unique_ptr 需要指定删除器类型
std::unique_ptr<int,void(*)(int*)>p2(new int(1), [](int*p)
{
	delete p;
})

2.4 unique_ptr 的一些基本操作

A:release 所有者放弃对指针的控制权返回指针,并将自身置为空
B:reset 如果直接调用reset 就是释放u的对象,如果reset§就是让u指向这个对象

3 weak_ptr弱引用的指针

3.1weak_ptr的相关知识

所谓的弱引用是区别与shared_prt的强引用的,shared_ptr的每个引用者都有权限去操作那个共同的指向的对象 而弱引用是不控制对象的生命周期的智能指针,它的出现是为了解决shared_prt相互引用导致的内存泄漏的问题,它可以从一个weak_ptr或者一个shared_ptr的对象构造,它的构造和析构不会引起shared_ptr引用计数的增加和减少,weak_ptr只是提供了一种方位shared_ptr的接口

3.2 weak_ptr的初始化

A: 初始化为空的weak_ptr weak_ptr w1
B 用shared_ptr来初始化

std::shared_ptr<int> sp(new int(1));
std::weak_ptr<int>wp(sp);
std::weak_ptr<int>wp2;
wp2 = sp;

3.3 weak_ptr的一些用法

A: 使用use_count 获取共享对象shared_ptr的数量

std::shared_ptr<int>p1(new int(1));
std::weak_ptr<int>wp1(p1);
wp1.use_count() ;//1

B:* 使用expired() 来判断对象是否已经被释放了*

std::shared_ptr<int>p1(new int(1));
std::weak_ptr<int>wp1(p1);
if(wp1.expired()) //use_count 为0就返回true
	cout << "释放了"
else
	cout <<"还没了" 

C: lock 获取监视的shared_ptr

std::weak_ptr<int>gw;
void f()
{
	if(gw.expired()){
		cout << "gw无效,资源已释放";
}else {
	auto spt = gw.lock();
	cout << "gw有效, *spt = " << *spt << endl;
	}
}

int main()
{
	{
		auto sp = std::make_shared<int>(42);
		gw = sp;
		f(); //可用
	}
	f();//不可用
	return ;
}

C:前面shared_ptr返回this指针就是通过弱引用实现的,shared_from_this();通过lock

D:weak_ptr解决循环引用问题

#include
#include
using namespace std;
class A;
class B;
class A
{
	public:
	std::weak_ptr<B> pb;
	~A(){cout << "A Destroy" <<endl;}
};
class B
{
public :
	std::weak_ptr<A>pa;
	~B(){cout <<"B Destroy"<<endl;}
};
int main()
{
	{
		auto pa = make_shared<A>();
		auto pb = make_shared<B>();
		pa->pb = pb;
		pb->pa = pa;
		cout<< pa.use_count ()<<endl; //1
        cout<<pb.use_count ()<<endl; //1
	}
	cout << "Main End Return" <<endl;
	return 0;
}

4 智能指针的安全性问题

:引用计数多线程是安全的,至于智能指针多线程是否安全需要分情况讨论

A * 多线程创建和开启的时候 通过lambda表达式或者函数参数引用传入智能指针都是不安全的 这样的话就存在多个线程使用一个智能指针*

B *所管理数据的线程安全性问题。显而易见,所管理的对象必然不是线程安全的,必然 sp1、sp2、sp3智能指针实际都是指向对象A, 三个线程同时操作对象A,那对象的数据安全必然是需要对象
A自己去保证。
*

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