侧边栏壁纸
博主头像
Into The Abyss 博主等级

My Life is a Death Race

  • 累计撰写 34 篇文章
  • 累计创建 7 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

c++_study_ptr

Administrator
2023-11-01 / 0 评论 / 0 点赞 / 184 阅读 / 0 字

new/delete探秘

new/delete不是函数,而是关键字/运算符。malloc/free用于c语言,new/delete用于c++。这两对都用于动态的在堆中分配和释放内存,new/delete比malloc/free干的事情更多,new/delete类对象时可以调用类对象的构造函数和析构函数,说明具备对堆上所分配内存进行初始化/释放的能力,malloc/free不具备。

operator new()和operator delete()是函数

new干了两件事:

1)分配内存,通过operator new()来分配内存

2)调用构造函数初始化内存

delete干了两件事:

1)调用析构函数

2)释放内存,通过operator delete()来释放内存

class A{
public:
    A(){};
    ~A(){};
}

int *pa = new int(100); //泄露4字节
int *pa2 = new int[2]; //泄露8字节
A *pA = new A(); //泄露1字节
A *pA2 = new A[2](); //泄露6字节,多了四个字节

int *pa3 = new int[3];
delete pa3; //没有用[],pa3没有发生内存泄露,正常为delete[] pa3;
A *pA3 = new A[2]();
delete pA3; //系统异常,规范写法delete[] pA3;

动态给类型A分配内存对象数组时多出来4个字节,而给内置类型int动态分配内存对象数组时没有。多出来的4个字节,用来记录数组元素的个数用来调用析构函数,无自定义的析构函数可以不带[],内置类型不需要调用析构函数,所以没有多4个字节

智能指针

int *p = new int();
int *q = p;
int *r = q;//只有p,q,r都不使用,才能释放这段内存

裸指针:直接用new返回的指针,这种指针强大、灵活,但是开发者需要全程负责维护,容易用错

智能指针:解决裸指针可能存在的各种问题,最突出的优点是自动释放所指向的对象内存。标准库中有4种智能指针,分别是auto_ptr,unique_ptr,shared_ptr,weak_ptr,帮助我们动态分配对象的声明周期的管理,能够有效防止内存泄露

auto_ptr(c++98),已经完全被unique_ptr取代,不要继续使用,剩下三种都是类模板,可以将new获得的地址赋给他们

share_ptr:共享式指针,多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放

weak_ptr:是辅助share_ptr工作的

unique_ptr:独占式指针,同一个时间内,只有一个指针能够指向该对象,该对象的所有权是可以移交出去的

你忘记delete的时候,智能指针能够帮助你delete

share_ptr

共享所有权,不是被一个share_ptr拥有,而是被多个share_ptr之间相互写作;share_ptr有额外开销;工作原理是引用计数,每个share_ptr的拷贝都指向相同的内存。所以,只有最后一个指针不需要指向该对象时,这个对象会被释放。

最后一个指向该内存对象的share_ptr在下列情况下会释放该对象

1)这个share_ptr被析构的时候

2)这个share_ptr指向其他对象

格式:share_ptr<指向的类型> 智能指针名

share_ptr<int> pi(new int(100));
share_ptr<int> pi2=new int(100); //不可以,智能指针是explicit,不可以进行隐式类型转换,必须直接初始化

share_ptr<int> makes(int value){
    return share_ptr<int>(new int(value));
}

make_shared函数

标准库里的函数模板,安全,高效的分配和使用shared_ptr;它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared_ptr,不能指定自己的删除器

shared_ptr<int> p2 = make_shared<int>(100);
shared_ptr<string> p3 = make_shared<string>(5,'a'); //aaaaa

不能自定义删除器

shared_ptr引用计数的增加和减少

引用计数的增加:每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象,在如下情况下,所有指向这个对象的shared_ptr引用计数都会增加

1)如下代码所示,用p6来初始化p7这个智能指针

2)把智能指针当作实参往函数中传递

3)作为函数返回值

void myfunc(shared_ptr<int> ptmp){
    return;
}
void myfunc2(shared_ptr<int> &ptmp){
    return;
}
shared_ptr<int> myfunc3(shared_ptr<int> &ptmp){
    return ptmp;
}

auto p6 = make_shared<int>(100);
auto p7(p6);  //智能指针定义时的初始化,p7和p6指向了相同的对象,引用计数增加
myfunc(p6);  //进入时引用计数增加,退出时引用计数减少
myfunc2(p6);  //引用计数不增加
auto p8 = myfunc3(p6);  //引用计数增加

引用计数的减少

1)给shared_ptr赋予新值,让该shared_ptr指向一个新对象

2)局部的shared_ptr离开作用域

3)当一个shared_ptr引用计数从1变到0,则他会自动释放自己所管理的对象

p8 = make_shared<int>(200); //p8指向新对象,计数为1,p6,p7引用计数减少
p9 = make_shared<int>(200);
p9 = p8; //给p9赋值会让p9指向p8所指向的对象,p8指向的对象引用计数变为2,p9之前指向的对象引用计数从1变为0

shared_ptr常用操作

use_count():返回多少个智能指针指向某个对象,主要用于调试

unique():是否该只能指针独占某个指向的对象,也就是说若只有一个智能指针指向某个对象,则unique()返回true

reset():重置,1)不带参数时,若pi是唯一指向该对象的指针,那么释放pi所指向的对象,并将pi置空;若pi不是唯一指向该对象的指针,那么不释放pi所指向的对象,但指向该对象的引用计数会减少1,并将pi置空

2)带参数时,若pi是唯一指向该对象的指针,那么释放pi所指向的对象,并将pi指向新对象;若pi不是唯一指向该对象的指针,那么不释放pi所指向的对象,但指向该对象的引用计数会减少1,并将pi指向新对象

解引用*:获得p指向的对象,与普通指针相同

get():返回p中保存的指针(裸指针),小心使用,如果智能指针释放了所指向的对象,那么这个返回的裸指针与人就变得不可用。不能delete保存的裸指针

swap():交换两个智能指针所指向的对象

p1 = make_shared<string>("test1");
p2 = make_shared<string>("test2");
std::swap(p1,p2);
p1.swap(p2)

=nullptr:1)将所指向的对象引用计数减1,若引用计数变为0,则释放智能指针所指向的对象

2)将智能指针置空

指定删除器以及数组问题

指定删除器,一定时机帮我们删除所指向对象;

void mydelete(int *p){  //我们的删除器,删除整形指针使用,当智能指针引用计数为0,就会调用该删除器删除对象
    delete p;
}

shared_ptr<int> p(new int(1234),mydelete);
shared_ptr<int> p2(p);
p2.reset();
p.reset();
//删除器可以是lambda表达式
shared_ptr<int> p3(new int(1234),[](int *p){
    delete p;
});

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

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

shared_ptr<int[]> p6(new int[10]);

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

weak_ptr

weak_ptr辅助shared_ptr工作

weak_ptr也是一个类模板,也是个智能指针。这个智能指针指向一个由shared_ptr管理的对象,但是weak_ptr这种指针不控制所指向的对象的生存周期。换句话说,将weak_ptr绑定到shared_ptr上不会增加shared_ptr的引用计数。当shared_ptr需要释放所指定对象的时候照常释放,不管是否有weak_ptr指向该对象

auto pi = make_shared<int>(100);
weak_ptr<int> piw(pi); //pi强引用计数不改变,弱引用计数加1
//强引用计数才能决定对象生存期,弱引用计数对生存期没有影响,只是监视作用

lock():功能是检查weak_ptr所指向的对象是否存在,如果存在,那么就返回一个指向该对象的shared_ptr(强引用计数加1);如果不存在,则返回一个空的shared_ptr

use_count():获取与该弱指针共享对象的其他shared_ptr的数量,获得当前所观测资源的强引用计数

expired():是否过期,弱指针use_count()为0,则返回true;判断所观测资源是否已经被释放

reset():将该弱引用指针设置为空,不影响指向该对象的强引用数量,但指向该对象的弱引用数量会减少

裸指针内存为4字节,shared_ptr,weak_ptr内存为8字节,包含一个指向类型对象的裸指针和一个指向控制块的指针,控制块中含有所指对象的强引用计数、弱引用计数、其他数据等

shared_ptr陷阱分析

慎用裸指针

void proc(shared_ptr<int> ptr)
{
    return;
}

int *p = new int(100);
proc(p);//报错,不能隐式转换
proc(shared_ptr<int>(p));//参数是一个临时shared_ptr,用一个裸指针显示构造
*p = 45; //潜在的不可预料的问题,因为p指向的内存已经被释放

shared_ptr<int> p2(p); //使用裸指针初始化智能指针后,不应该再使用裸指针来访问相应的内存

不要用裸指针初始化多个shared_ptr,因为初始化的多个shared_ptr无关联,释放时每一个都会自动释放,造成同一块内存被多次释放

慎用get()返回地指针

get()的作用是返回智能指针所对应的裸指针,返回地指针不能delete,不能将其他智能指针绑定到get返回地指针上

不要把类对象指针this作为shared_ptr返回,改用enable_shared_from_this

class CT
{
public:
    shared_ptr<CT> getself()
    {
        return shared_ptr<CT>(this);//相当于用裸指针初始化了多个shared_ptr
    }
}
shared_ptr<CT> pct1(new CT);
shared_ptr<CT> pct2 = pct1; //正常,两个强引用
shared_ptr<CT> pct3 = pct1->getself();  //出现问题,pct1和pct3无关联,指向同一块内存

//正常返回只能指针
class CT : public enable_shared_from_this<CT>
{
public:
    shared_ptr<CT> getself()
    {
        return shared_from_this(); //返回智能指针
    }
}


再调用shared_from_this()这个方法时内部实际调用了lock()方法,使得shared_ptr指针计数加1,同时返回这个shared_ptr

避免循环引用

unique_ptr

独占式指针,同一时刻只能有一个unique_ptr指针指向这个对象;当这个unique_ptr被销毁时,他所指向的对象也被销毁

unique_ptr<int> pi(new int(100));

make_unique()

c11中没有,c14加入,不支持指定的删除器语法,如果不使用删除器,建议优先使用。

unique_ptr<int> p1 = make_unique<int>(100);
auto p2 = make_unique<int>(100);

unique_ptr不支持的操作

不支持赋值、拷贝

unique_ptr<string> ps1(new string("i love china"));
unique_ptr<string> ps2(ps1); //不支持
unique_ptr<string> ps3 = ps1; //不支持

unique_ptr常用操作

移动语义

unique_ptr<string> ps1(new string("i love china"));
unique_ptr<string> ps2 = std::move(ps1); //移动完成后,ps1为空,ps2指向之前ps1所指

release():放弃对指针的控制权。返回裸指针,将该裸指针置空;返回地这个裸指针我们可以手工delete来释放,也可以用来初始化另外一个智能指针

unique_ptr<string> ps1(new string("i love china"));
ps1.release(); //内存泄露
string *temp = ps1.release();
delete temp;

reset():不带参数时,释放智能指针所指向的对象,并将智能指针置空;带参数时,释放智能指针所指向的对象,并让该智能指针指向新对象

=nullptr:释放智能指针所指向的对象,并将智能指针置空

指向一个数组

unique_ptr<int> ptrarray(new int[10]);
ptrarray[0] = 1;
ptrarray[1] = 2;

class A
{
public:
    A(){};
    ~A(){};
}
unique_ptr<A> ptrarray(new A[10]); //因为自定义析构函数,所以报异常
unique_ptr<A[10]> ptrarray(new A[10]); //不报异常

get():返回智能指针中的裸指针

*解引用:获取该智能指针指向的对象

swap():交换两个智能指针指向的对象

转换为shared_ptr类型:如果unique_ptr为右值,就可以把他赋值给shared_ptr。因为shared_ptr包含一个显式的构造函数,可用于将右值unique_ptr转换为shared_ptr,shared_ptr将接管原来归unique_ptr所拥有的内存

unique_ptr<string> ps1(new string("i love china"));
shared_ptr<string> ps2 = std::move(ps1);

返回unique_ptr

unique_ptr<string> tuniqp(){
    unique_ptr<string> pr(new string("i love china"));
    return pr;
}

unique_ptr<string> ps = tuniqp(); //临时对象直接构造在ps中,,如果不接,则临时对象会被释放,同时释放相应的内存

指定删除器

delete相当于默认删除器

void mydelete(string *pdel){
    delete pdel;
    pdel = nullptr;
}
typedef void(*fp)(string*);
unique_ptr<string,fp> ps1(new string("i love china"),mydelete);

//第二种写法
using fp2 = void(*)(string *);
unique_ptr<string,fp2> ps1(new string("i love china"),mydelete);

//第三种写法
typedef decltype(mydelete)* fp3;
unique_ptr<string,fp3> ps1(new string("i love china"),mydelete);

//第四种写法
unique_ptr<string,decltype(mydelete)*> ps1(new string("i love china"),mydelete);

//第五种写法,lambda表达式
auto mydella = [](string *pdel){
    delete pdel;
    pdel = nullptr;
}
unique_ptr<string,decltype(mydella)> ps1(new string("i love china"),mydella);

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

但是unique_ptr不一样,指定unique_ptr的删除器会影响unique_ptr的类型

尺寸问题

通常情况下,unique_ptr尺寸与裸指针一样,应该为4字节;如果增加了自己的删除器,则unique_ptr的尺寸可能增加,也可能不变,lambda表达式为删除器,则尺寸不变;定义一个函数为删除器,则尺寸增加。

shared_ptr不管指定什么删除器,尺寸为裸指针的2倍

总结

智能指针的主要目的是:帮助我们释放内存,以防止我们忘记释放内存造成内存泄露(设计思想)

auto_ptr被废弃:不能再容器中使用;不能从函数中返回auto_ptr;具有unique_ptr的部分特性

如果程序要使用多个指向同一个对象的指针,应该选择shared_ptr;

如果程序不需要多个指向同一个对象的指针,应该选择unique_ptr,性能更好;

0
c++

评论区