shared_ptr的初始化可以通过裸指针初始化,返回值初始化,和make_shared函数初始化。
1)通过裸指针与返回值初始化
shared_ptr<int> make(int value){
return shared_ptr<int>(new int(value));
}
void test02(){
shared_ptr<int> pi1(new int(100)); //裸指针初始化,pi指向一个值为100的int型数据
shared_ptr<int> pi2 = make(200);//返回值初始化
cout<< (*pi1.get()) << endl;
cout<< (*pi2.get()) << endl;
}
int main(){
test02();
return 0;
}
2)通过make_shared函数初始化
//1 性能更好(高效)
/*
①性能更好(高效):用new来构造shared_ptr指针,那么new的过程是一次堆上面的内存分配,
而在构造shared_ptr对象的时候,由于需要使用堆上面共享的引用计数(指针),又需要在堆上面
分配一次内存,即需要分配两次内存,而如果用make_shared函数,则只需分配一次内存,
所以性能会好很多。
伪代码解释:下面可以看到,ptr传进来已经new一次,然后引用计数_pRefCount也会new一次
造成两次分配内存。
*/
template <class T>
class SharedPtr
{
public:
//
SharedPtr(T* ptr = nullptr): _ptr(ptr), _pRefCount(new int(1)), _pMutex(new mutex){
// 如果是一个空指针对象,则引用计数给0
if (_ptr == nullptr)
*_pRefCount = 0;
}
private:
int* _pRefCount; // 引用计数
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
}
//2 更安全
/*
②更加安全:若我们使用裸指针对shared_ptr构造时,包含两步操作:
(1)new一个堆内存。
(2)分配一个引用计数区域管理该内存空间(指上面的_pRefCount)。
但是并没有保证这两个步骤的原子性,当做了第(1)步,没有做第二步如果程序抛出了异常,将导致内存泄露。
而make_shared内部有对这两个步骤合成一步进行处理,
因此更推荐使用make_shared来分配内存。
*/
//3 make_shared缺点
/*
虽然make_shared针对裸指针更好,但它也有缺点。
③缺点:make_shared一次性分配堆内存的做法,在释放的时候可能会导致内存延迟释放,
因为如果有weak_ptr持有了指针,引用计数不会释放,而引用计数和实际的对象分配在同一块堆内存,
因此无法将该对象释放,如果两块内存分开申请,则不存在这个延迟释放的问题。
*/
//4 总结
/*
实际开发可能不会考虑这些,使用裸指针或者make_shared都行,但是大家一定要知道它们可能存在的问题。
*/
在上面介绍为何使用make_shared顶替裸指针初始化后,下面正式说明make_shared如何初始化shared_ptr。
#include
#include
#include
using namespace std;
shared_ptr<int> make(int value){
return shared_ptr<int>(new int(value));
}
void test02(){
// shared_ptr pi1(new int(100)); //pi指向一个值为100的int型数据
// shared_ptr pi2 = make(200);
// cout<< (*pi1.get()) << endl;
// cout<< (*pi2.get()) << endl;
shared_ptr<int> p1 = make_shared<int>(100);
shared_ptr<string> p2 = make_shared<string>(5, 'a'); //类似于string mystr(5, 'a')
shared_ptr<int> p3 = make_shared<int>();//默认初值为0,cout<< (*p3.get()) << endl;
p3 = make_shared<int>(300); //指向新int,p3首先释放指向值为0的内存,然后指向这个新的300的内存
auto p4 = make_shared<string>("I love you");
cout<< (*p1.get()) << endl;
cout<< p2.get()->data() << endl;
cout<< (*p3.get()) << endl;
cout<< p4.get()->data() << endl;
}
int main(){
//test01();
test02();
return 0;
}
3)注意,shared_ptr不支持隐式转换。
//例1
shared_ptr<int> pi2 = new int(200); //不可以,智能指针是explicit,不可以进行隐式类型转换,必须直接初始化形式
//例2
shared_ptr<int> make(int value){
return new int(value); //error,无法把new得到的int *转换成shared_ptr
return shared_ptr<int>(new int(value))//正确写法
}
引用计数就是多个shared_ptr共用一片内存的shared_ptr个数。当一个shared_ptr开辟一个内存,引用计数为1,然后用该shared_ptr初始化其它shared_ptr那么就是共用一个内存,引用计数为2,以此类推。而当一个shared_ptr指向其它shared_ptr的内存,或者shared_ptr生命周期结束被析构时,引用计数都会减少。
#include
#include
#include
using namespace std;
void Func1(shared_ptr<int> a)
{
cout<<"函数1内:"<<endl;
cout<<"值的引用数为: "<<a.use_count()<<endl; // 2 调用完毕a被释放 引用计数减1
cout<<"函数1结束。"<<endl;
}
shared_ptr<int> Func2(shared_ptr<int>& a)
{
cout<<"函数2内:"<<endl;
cout<<"引用的引用数为: "<<a.use_count()<<endl; // 1引用不会增加引用计数
cout<<"函数2结束。"<<endl;
return a;
}
//主要测试参数为值,引用和返回值对引用计数的影响
void test03(){
shared_ptr<int> sh1(new int(10)); // 构造一个指向int类型对象的指针sh1,引用计数为1
cout<<"Ref count: "<< sh1.use_count() << endl;
{
shared_ptr<int> sh2 = sh1;
cout<<"Ref count: "<< sh1.use_count() << endl;
}
//sh2生命周期结束后
cout<<"Ref count: "<< sh1.use_count() << endl;
cout<<endl;
//1 测试参数为值
Func1(sh1);
cout<<endl;
//2 测试参数为引用
Func2(sh1);
cout<<endl;
//3 测试接收匿名对象
shared_ptr<int> sh3 = Func2(sh1);
cout<<"sh3加入之后的引用数:"<<sh3.use_count()<<endl;
}
//测试引用计数增加减少
void test04(){
auto p1 = make_shared<int>(100);
auto p2(p1);
auto p3 = Func2(p2);
cout<<"Ref count: "<< p1.use_count() << endl;
p3 = make_shared<int>(200); //p3指向新对象,计数为1,p1、p2指向对象计数恢复为2;
p2 = make_shared<int>(300); //p2指向新对象,计数为1,p1指向对象的计数恢复为1;
cout<<"p3,p2改变指向后,Ref count: "<< p1.use_count() << endl;
{
shared_ptr<int> p4 = p1;
cout<<"Ref count: "<< p1.use_count() << endl;
}
cout<<"p4生命周期结束后,Ref count: "<< p1.use_count() << endl;
}
int main(){
//test01();
//test02();
test03();
cout<<endl;
cout<<"====test04 begin===="<<endl;
test04();
return 0;
}
从上面结果可以得出:
上面是针对于会使引用计数增加的总结。下面总结引用计数减少的。
4.1 use_count()
use_count()是获取该片内存有多个shared_ptr个对象正在引用。
4.2 unique()
unique:是否该智能指针独占某个指向的对象,也就是若只有一个智能指针指向某个对象,则unique()返回true,多个返回fasle。(为空时也不属于独享)
4.3 reset()与shared_ptr的比较
reset()函数分为无参与有参的使用。
看例子。
//shared_ptr的比较 与 reset函数方法
void test05(){
shared_ptr<int> sh1=make_shared<int>(3);
cout<<sh1.use_count()<<endl; // count=1
shared_ptr<int> sh3=sh1;
cout<<sh1.use_count()<<endl; // count=2
//1 比较 重载了==与!=运算符 实际上当成指针比较就好了
if(sh1!=NULL && sh3!=NULL){
cout<<"sh1和sh3不为空!"<<endl;
}
//2.1 无参reset函数 使调用的shared_ptr指向一个空资源 只是该shared_ptr的引用计数变为0 其他的因为sh1调用reset而减1
sh1.reset(); // 使sh1指向的count为0
cout<<sh1.use_count()<<endl; // count=0
if (sh1 == nullptr){
cout << "sh1被置空" << endl;
}
cout<<sh3.use_count()<<endl; // count=1
//2.2 有参reset 使shared_ptr指向参数new出的资源对象
sh1.reset(new int(5)); // sh1指向该参数new出的资源
cout<<sh1.use_count()<<endl; // count=1
}
4.4 解引用的意思
解引用就是获取该裸指针的对象。
例如。
shared_ptr<int> p(new int(123));
cout << *p << endl;
4.5 get()
get():获取裸指针操作。考虑到有些函数的参数需要的是一个内置裸指针,而不是智能指针。例如上面的初始化使用过get函数。
4.6 swap()
swap():交换两个智能指针所指向的对象。
shared_ptr<string> ps1(new string("1111111"));
shared_ptr<string> ps2(new string("2222222"));
std::swap(ps1, ps2); //交换ps1指向222222222
ps1.swap(ps2); //在交换ps1指向11111111
分析:当传给shared_ptr构造函数不止一个对象时,例如是一个对象数组时,因为shared_ptr析构默认用的是delete a,只能delete掉一个对象,所以我们需要自定义回调函数,用于析构传进的对象数组。
5.1 使用函数模板作为自定义删除器
//用来释放malloc出来的函数对象
template<class T>
class FreeFunc{
public:
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
//用来释放new[]出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
//用来释放文件描述符的函数对象
template<class T>
class ClosefdFunc{
public:
void operator()(T* fd)
{
cout << "close fd" << fd << endl;
fclose(fd);
}
};
void test06(){
FreeFunc<int> Object1;
shared_ptr<int> sp1((int*)malloc(sizeof(int)*4), Object1); // 回调函数是可调用对象,可以是普通的函数名或者函数对象或者lambda表达式
DeleteArrayFunc<int> Object2;
shared_ptr<int> sp2(new int[4], Object2);
ClosefdFunc<FILE> Object3;
shared_ptr<FILE> sp3(fopen("myfile.txt","w"), Object3);
}
结果看到,三个自定义析构函数均被调用,并且可以发现文件对象会被编译器优先释放。
5.2 使用其它新特性作为删除器
class A {
public:
A() {};
~A() {};
};
void test07(){
shared_ptr<A> p(new A[10], std::default_delete<A[]>());
}
void test08(){
shared_ptr<A[]> p1(new A[10]);//A类在上面
shared_ptr<int[]> p2(new int[10]);
p2[0] = 12;
p2[1] = 15;
cout<<p2[0]<<endl;//输出12
cout<<p2[1]<<endl;//输出15
}
5.3 模板+lambda表达式实现开发时常用的删除器
由于我们开发时很少使用到像5.1这种模板,因为比较长,且需要额外定义对象传入,所以我们开发时更倾向使用lambda表达式,代码更少。但是并不是不能使用5.1这种方法。
template <class T>
shared_ptr<T> my_make_shared(){
return shared_ptr<T>(new T,[](T *ptr){delete ptr,ptr=nullptr;});
}
void test09(){
//对于类中没有成员的,只有使用裸指针
shared_ptr<int> sh1 = my_make_shared<int>();
*sh1.get() = 2;
cout<<*sh1.get()<<endl;
//对于类中有成员的,可以直接使用智能指针
shared_ptr<A> sh2 = my_make_shared<A>();
sh2->SetI(100);
cout<<sh2->GetI()<<endl;
}
template<typename T>
shared_ptr<T> my_make_shared_array(size_t size)
{
return shared_ptr<T>(new T[size], default_delete<T[]>());
}
void test10(){
shared_ptr<A> parray = my_make_shared_array<A>(15);
parray->SetI(200);
//parray.get()[0].SetI(100);//也行,但直接使用智能指针更安全
cout<<parray.get()[0].GetI()<<endl;//输出200
}
5.4 指定删除器额外说明
就算是两个shared_ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared_ptr也是属于同一类型,所以它们可以改变指向,改变指向后,若引用计数变为0,则先用就的删除器删除内存,然后它将使用新的删除器。
例如。
auto lambda1 = [](int *p)
{
delete p;
};
auto lambda2 = [](int *p)
{
delete p;
};
shared_ptr<int> p1(new int(100), lambda1);
shared_ptr<int> p2(new int(100), lambda2);
p2 = p1; //p2会先调用lambda2把自己所指向的对象释放,然后指向p1所指向的对象。p1所指向
//的对象引用计数为2,整个main执行完成后还会调用lambda1来释放p1,p2共同指向的
//对象
并且注意,我们5.3常用删除迭代器为何不使用make_shared,因为使用make_shared这种方法我们无法指定自己的删除器。所以只能使用new。