C++入门(智能指针和并发)

本文深入探讨了C++中的智能指针,包括`shared_ptr`、`unique_ptr`和`weak_ptr`的使用场景、陷阱和性能。讲解了动态内存管理以及`new`与`delete`的使用规则。此外,文章还详细阐述了C++并发与多线程的概念,介绍了线程的创建、同步与通信机制,如互斥量、条件变量、`std::async`和`future`等。
摘要由CSDN通过智能技术生成

文章目录

智能指针

直接内存管理(new/delete)

new分配方式称为动态分配(分配在堆上):直接内存管理(new.delete)

void func()
{
   
	int i;
	i = 5;//临时对象,系统在栈上进行分配,当函数结束内存被回收

	static int j;//局部静态对象,在静态区域分配,执行到此语句时才会分配内存,函数执行完毕也不会释放内存
}

class A

A a;//编译器创建a对象,在main结束后释放

初始化

int *point = new int;//动态分配,初值未定义,为随机数
int *point = new int(100);//用()给初值

string *mystr = new string;//空字符串,说明调用了string的默认构造函数
string *mystr2 = new string(5,'a');//生成一个字符串,‘aaaaa’

vecctor<int> *pointv = new vector<int>{
   1,2,3,4,5};//new容器对象,里面有五个元素

//值初始化,加一个()
string *mystr3 = new string();//也是空字符串
int *pointv = new int();//0值
//对类来说,值初始化没有区别
A *pa = new A;//新建对象
A *pa = new A();

new对象的时候,能值初始化就值初始化,防止随机值,没有坏处

auto可以和new配合使用

auto mystr3 = new auto (mystr2);//用2初始化3,即mystr3 = string**,是指针的指针

const对象动态分配

const int *pointci = new const int(200);

new和delete说明:
成对使用,delete的作用是回收用new分配的内存。不是new的,不能delete;
delete只能一次,不能多次,delete后内存就不能使用;
指向同一块内存的也不能重复释放;

MFC微软公司的一个基础程序框架(MFC基础类库),可以生成一个带窗口的程序框架,简化了很多界面开发工作。可以用来发现内存泄漏问题。

new/delete

是个关键字/运算符,不是函数,类似sizeof;
new和malloc的最主要区别:new具备对堆上所分配内存进行初始化/释放的能力,可以调用构造函数

//类A
A *pa =new A();//new调用构造函数
delete pa;//delete调用析构函数
operator new() operator delete()(一般不直接用)

operator new()是函数,new在分配内存时就是通过operator new()来分配内存,delete在释放内存就是通过operator delete()来释放内存

int *pi = new int;
delete pi;
//
void *myorgpoint = operator new(100);
new如何分配内存
int *p = new int;//4字节,有记录机制记录分配的内存大小
delete p;
申请和释放一个数组

注意格式

int *p = new int[2];//8个字节
delete[]p;//注意数组释放的格式 

A a;
int ilen = sizeof(a);//空类是一个字节
A *pa = new A();//类的成员函数不占用类的字节,但是成员函数会占用类的字节
A *pa = new A[2]();//两个对象的数组,占六个字节,多出来的四个字节用来保存数组元素的个数2,但只针对是类类型
delete pa;//错误,对于类类型构成的数组,必须加[]
delete[]pa;

配对使用

内置类型比如int并没有调用构造函数,所以没有多分配4个字节;
对于int,new[], delete p,delete[]p效果一样;
如果一个对象,使用new[]分配内存,却用单独的delete(而不是delete[])释放内存,那么需要满足的类型时内置类型或者无析构函数的类类型;
[]也要配对使用

智能指针
int *p = new int();
int *q = p;
int *r = p;

pqr都指向同一段内存,只有pqr都不再使用了的时候,才能释放这段内存
p裸指针:直接用new返回的指针,需要全程维护,容易出错
指针指针解决裸指针带来的各种问题:对裸指针进行包装,最突出的是能自动释放所指向的对象内存,不用担心忘记释放。使用智能指针更便于编写和调试。
四种智能指针:auto_ptr(98,弃用), unique_ptr(11,独占式指针), shared_ptr(11,共享式指针), weak_ptr(11,辅助shared_ptr指针);
这三种指针指针都是类模板,vector,可以将new获得地址赋给它们;
智能指针本质就是不用手动delete

shared_ptr

共享所有权,不是被一个shared_ptr拥有,而是多个之间相互协作;
工作原理:引用计数,每个shared_ptr的拷贝都指向相同的内存,所以只有最后一个指向该内存的指针不需要再指向该对象时,这个shared_ptr才会去析构所指向的对象;
最后一个指向该内存的shared_ptr在以下情况下会释放该对象:
a)这个shared_ptr被析构的时候;
b)这个shared_ptr指向其他的对象的时;
类模板,用<>,就是指针可以指向的类型,再跟智能指针名
1)常规初始化

shared_ptr<int>pi(new int(100));//pi指向一个值为100的int数据

shared_ptr<int>makes(int value)//函数
{
   
	return shared_ptr<int>(new int(value))
} 
shared_ptr<int>pi3 = makes(130);

2)make_shared函数
标准库里的函数模板,安全高效的分配和使用shared_ptr;
能在动态内存(堆)中分配病初始化一个对象,然后返回指向此对象的shared_ptr

shared_ptr<int>p2=male_shared<int>(100);//这个shared_ptr返回一个值为100的整型内存,类似int *p = new int (100)
shared_ptr<string>p2 = make_shared<string>(5,'abc');//
shared_ptr<int>p4 = make_shared<int>();//p4指向一个int,值初始化
p4 = make_shared<int>(400);//p4指向新的int,里面保存的时400,首先释放刚才为0的内存,然后指向400的内存(智能)

auto p5 = make_shared<string>(5,'a');//

3)shared_ptr引用计数的增加和减少
共享式,引用计数,每一个shared_ptr的拷贝都指向相同的内存,只有最后一个指向该对象的shared_ptr指针不需要再指向该对象的时候,这个shared_ptr才会析构所指向的对象。

引用计数的增加:
每个shared_ptr都会记录有多少其他的shared_ptr指向相同的对象;

auto p6 = make_shared<int>(100);//此对象目前有一个引用者
auto p7(p6);//智能指针定义时的初始化,p6和p7指向了相同的对象,此对象目前有两个引用者

用p6初始化p7,引用计数增加1;
把智能指针当做实参往函数里传,引用计数增加1;(实参也指向该内容)
但是给函数传递引用的话就不会增加;
作为函数的返回值,当用指针来接时,引用计数加1,否则不加;

有指针指向新对象的时候,计数减一;
局部的shared_ptr离开作用域,计数减一;(退出函数)
当一个shared_ptr引用计数变成0,会自动释放自己所管理的对象;

2)shared_ptr常用操作
use_count():返回多少个智能指针指向该对象,主要用于调试;

shared_ptr<int> myp(new int(100));
int icout = myp.use_count();//1
shared_ptr<int> myp2(myp);
icout = myp.use_cout();//2
shared_ptr<int>myp3;
myp3 = myp2;
icout = myp3.use_count();//3
icout = myp.use_cout();//3

unique():是否该智能指针独占该对象,也就是若计数为1,则返回True,否则相反

shared_ptr<int>myp(new int (100));
//shared_ptr<int> myp2(myp);
if (myp.unique())
{
   
	cout<< "OK" << endl;
}

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

shared_ptr<int>pi(new int(100));
pi.reset():
if (pi == nullptr )
{
   
	cout << "pi置空"<< endl;
}

带参数时:若pi时唯一指向该对象的指针,则释放pi指向的对象,让pi指向该对象;若pi不是唯一指向该对象的指针,则不释放该对象,但计数减一,同时让pi指向该对象。

shared_ptr<int>pi(new int(100));
pi.reset(new int(1));//释放原内存,指向该内存
if(pi.unique())
{
   
	cout << "unique" <<endl;
}

空指针

shared_ptr<int>p;
p.reset(new_int(1));//释放p指向的对象,因为为空所以让p直接指向新对象

*解引用:获得p指向的对象

shared_ptr<int>pi(new int(100));
cout << *pi << endl;//打印出100

get()
考虑到有些函数的参数需要的是一个内置指针而不是智能指针;
p.get():返回p中保存的指针(裸指针)

shared_ptr<int>myp(new int(100));
int *p = myp.get();
*p = 43;

swap()交换两个智能指针指向的对象(不常用)

=nullptr
将所指向的对象引用计数减一,若引用计数为0,则释放对象;
将智能指针置空

shared_ptr<string>psl(new string("abc"));
psl = nullptr;

智能指针名字作为判断条件

shared_ptr<string>psl(new string("abc"));
if(psl)
{
   
	cout << "psl指向一个对象" <<endl;
}

指定删除器以及数组问题
指定删除器:一定时机帮我们删除指向的对象;delete将delete运算符作为默认的资源析构方式。也可以指定自己的删除器取代系统默认的删除器,当智能指针需要删除所指向的对象时,编译器就会调用我们自己的删除器。
shared_ptr一般只需要在参数中添加具体的删除器函数名即可。

void myDelete(int *p)//删除器,用来删除整形指针用,当智能指针引用为0时,就会自动调用该删除器删除对象。
{
   
	delete p;//必须要的
}
void main()
{
   
	shared_ptr<int>p(new int (123),myDelete);//添加删除器
	shared_ptr<int>p2(p);//两个引用计数
	p2.reset();//剩一个引用计数
	p.reset();//释放该对象,调用删除器,同时p置空
	
}

删除器还可以是一个lambda表达式:

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

有些情况下,默认的删除器处理不了:动态数组
当数组中是类类型的时候,必须使用delete[]进行释放,此时系统默认的delete会报错,所以需要自己的删除器

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

shared_ptr<A>pA(new A[10]);//

shared_ptr<A>pA(new A[].[](A *p){
   delete[]p;})

用default_delete做删除器,可以删除数组

shared_ptr<A>pA(new A[10],std::fault_delete<A[]>);//使用A[]表示这是一个数组

在定义数字的时候在<>中加[]

shared_ptr<A[]>pA(new A[10]);//在定义时加A[],就可以使用系统默认的delete了
shared_ptr<int[]>pA(new int[10]);
p[0]= 12;//这种定义方式还支持下标

就算是两个shared_ptr指定了不同的删除器,只要指向的对象类型相同,那么这两个shared_ptr也属于同一个类型,可以放到一个容器中。

vector<shared_ptr<int>pvec(p1.p2)>;

注意使用make_shared后无法指定删除器。

weak_ptr

辅助shared_ptr(强指针)工作,是弱指针,也是个类模板。
weak_ptr绑定到shared_ptr上不会改变引用计数,其析构和构造函数不会影响引用计数。
当shared_ptr需要释放所指定的对象照常释放,不管是否有weak_ptr指向该对象。
能力弱,控制不了所指向对象的生存期。

弱引用的作用:监视shared_ptr(强引用)的声明周期,是一种对shared_ptr能力的扩充,不是独立的智能指针,不能操作所指向的资源。

auto pi = make_shared<int>(100);
weak_ptr<int>piw(pi);//piw弱共享pi,pi强引用计数不改变,但是弱引用计数会改变;强引用计数才决定对象生存期,弱引用计数不会。
weak_ptr<int>piw2;
piw2 = piw;//现在pi是强引用,piw和piw2是弱引用

lock():检查weak_ptr所指向的对象是否存在,如果存在,lock()就返回一个指向该对象的shared_ptr,如果不存在,就返回一个空的shared_ptr。

auto pi2 = piw.lock();//pi2是一个shared_ptr,引用计数加1,
if (pi2 != nullptr)//说明对象存在
{
   
	cout << "对象存在" << endl;
	*pi2 = 12;//改变对象值
}
else
{
   
	cout << "对象不存在" << endl;
}

weak_ptr能够判断指向的对象是否存在

常用操作
use_count():获取与该弱指针共享对象的其他shared_ptr的数量,即获得强引用计数

auto pi = make_sjared<int>(100);
auto pi2(pi);
weak_ptr<int>piw(pi);
int isc = piw.use_count();//2个
cout << isc << endl;

expired():是否过期的意思,表示指向的对象不存在,返回True

auto pi = make_sjared<int>(100);
auto pi2(pi);
weak_ptr<int>piw(pi);
int isc = piw.use_count();//2个
cout << isc << endl;
pi.reset();//释放,1个
pi2.reset();//释放,0个
if(piw.expired())
{
   
	cout << "对象已经过期" << endl;
}

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

lock()

auto p1 = male_shared<int>(42);
weak_ptr<int>pw;
pw = p1;
if (pw.expired())
{
   
	auto p2 = pw.lock();//返回一个shared_ptr,并且强引用计数加1
	if (p2 != nullptr) 
	{
   
		//
	}
}
	
//当离开p2的范围,引用计数重新变为1

尺寸问题
weak_ptr和shared_ptr的尺寸一样大,是裸指针的两倍,即8个字节;其中4个字节的裸指针指向对象,4个字节指向很大的数据结构(控制块),控制块里有所指向对象的强引用计数和弱引用计数和其他数据。

shared_ptr使用场景、陷阱、性能说明、尺寸

如果不同shared_ptr变量来接受myfunc返回的结果,那么myfunc所产生的shared_ptr就会被销毁。

慎用裸指针:裸指针到智能指针之间不能进行隐式转换,必须显式定义;把一个普通裸指针绑定到shared_ptr上之后,智能指针负责内存管理,不能再用裸指针访问shared_ptr管理的内存了。一定不要用裸指针初始化多个shared_ptr,初始化一个智能指针之后不能再使用裸指针,否则会导致内存被释放两次产生异常。

慎用get()返回的指针:返回智能指针指向的对象对应的裸指针。get返回的指针不能delete,否则会产生异常。不能将其他智能指针绑到get()返回的指针上,和上面是一个道理。即get()得到的指针不能用来初始化另个一智能指针或者对另一个智能指针进行赋值。

不要把this指针作为作为shared_ptr返回,改用enable_shared_from_this。在外面创建CT对象的智能指针以及通过CT对象返回的this指针智能指针都是安全的。

避免循环引用,防止内存泄漏。

shared_ptr的尺寸是裸指针的2倍,weak_ptr尺寸是裸指针的2倍;
第一个裸指针指向的是这个智能指针所指向的对象,第二个裸指针指向一个很大的数据结构(控制块),这个控制块里边有引用计数、弱引用计数、其他数据(自定义删除器、内存分配器等)。这个控制块是由第一个指向对象的shared_ptr创建的。
控制块创建时机:make_shared分配并初始化一个对象,返回指向此对象的shared_ptr,所以,它总是能够创建一个控制块;

移动语义:复制要增加引用计数,移动不需要;移动构造函数快过构造函数,移动复制运算符快过拷贝赋值运算符。

unique_ptr

独占式的概念(专属所有权):同一时刻只能有一个unique_ptr指针指向这个对象(这块内存);当这个unique_ptr被销毁的时候,它所指向的对象也被销毁。
格式:
unique_ptr<指向的对象类型>智能指针变量名

unique_ptr<int>pi;//pi指向一个int的空智能指针
if (pi == nullptr)
{
   
	cout << "pi还是空指针" << endl;
}
unique_ptr<int>pi2(new int(105));//pi2指向105的int对象
make_unique函数

不支持指定的删除器语法,如果不用删除器,建议使用。

unique_ptr<int>p1 = make_unique<int>(100);
auto p2 = make_unique<int>(200);
unique_ptr<int>pi2(new int (105));

不支持的操作(独占式)

unique_ptr<string>ps1(new string("abc"));
unique_ptr<string>ps2(ps1);//不支持此操作,该智能指针不支持拷贝动作
unique_ptr<string>ps3 = ps1;//不支持
unique_ptr<string>ps4;
ps4 = ps1;//不支持

移动语义

unique_ptr<string>ps1(new string "abc");
unique_ptr<string>ps2 = std::move(ps1);//移动后,ps1为空,ps2指向原来ps1所指

release():放弃对指针的控制权(切断智能指针和其他所指向的对象之间的联系)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值