16.2 new/delete探秘、智能指针总述与shared_ptr基础

一:new/delete(关键字/运算符)探秘

<1>new、delete是什么?
sizeof(关键字 / 运算符),不是函数;
new / delete(关键字 / 运算符),不是函数;
malloc / free(主要用于C语言编程中),而 new / delete 用于C++编程:这两对都用于动态的在堆中分配 / 释放内存;
new / delete比malloc / free干的事情更多;

A *pa = new A(); //构造函数被调用
delete pa; //析构函数被调用

new / delete具备对堆上所分配内存进行初始化 / 释放(反初始化)的能力(也就是调用构造函数和析构函数),而这些能力是malloc/free所不具备的。

<2>operator new() 和 operator delete(): 函数

int *pi = new int;
delete pi;
pi = nullptr;

new干了两件事情:
1>分配内存(通过operator new()来分配内存) ;
2>调用构造函数来初始化内存。
void* myorgpoint = operator new (100); //分配100字节,一般没人这么干,

delete也干了两个事情:
1>调用析构函数;
2>释放内存(调用operator delete()来释放内存);

<3>基本new如何记录分配的内存大小供delete使用
不同的编译器new内部有不同的实现方式;

int *p = new int; //4个字节;
delete p; //删除的时候,编译器怎么知道要回收4个字节,new内部有记录机制,记录了分配出去多少内存。

<4>申请和释放一个数组
A *pA = new A(); //泄漏1一个字节;
A *pA = new A2; //这里泄漏的是6个字节,而不是2个字节,多出来的4个字节是干啥的;
int *p = new int[2]; //int 数组,有2个元素,泄漏8字节,没有多出来4个字节;
疑问:为什么动态类型 A 分配内存对象多出来4个字节,而给内置类型 int 动态分配内存对象数组时并没有多出来 4 个字节。
用来记录需要析构多少次

new[] 应该使用delete[]释放
int *p = new int[3];

delete p; //没有用[],似乎也可以直接释放p这个int数组,没有发生内存泄漏
delete[]p; //这种释放才是规范的,没有任何问题

int* p = new int(100); //分配4字节,给int = 100;

class A
{
public:
	A()
	{
		cout << "A()构造函数执行,  this = " << this << ", threadId = " << this_thread::get_id() << endl;
	}
	~A()
	{
		cout << "~A()析构函数执行" << this << ", threadId = " << this_thread::get_id() << endl;
	}

public:
	int m_i;
};

int main()
{
	cout << "主线程id = " << this_thread::get_id() << endl;
	A a;
	int ilen_1 = sizeof(a);  //类对象肯定有地址,你就得至少占1个字节的地方。

	A* pa_1 = new A();  //泄露1字节
	int ilen_2 = sizeof(pa_1);

	A* pa_2 = new A[2]();  //这里泄漏6字节,而不是2字节,多出来4字节;这四个字节用来干嘛的?
	int* p = new int[2];  //int数组,有两个元素,泄漏8字节,没有多出四个字节来呢?
	cout << "main主函数执行结束!" << endl;
	return 0;
}

疑问:为什么动态给类型A分配内存对象数组时多出来4个字节,
而给内置类型int动态分配内存对象数组时并没有多出来4字节。

new[] 应该用delete[] 释放
int* p = new int[3]; //int内置类型;
这里分配了12字节,说明系统并没有多分配出4字节,否则这里就是16字节了。
delete p; //没有用[],似乎也可以直接释放p这个int数组,没有发生内存泄漏。
delete[] p; //这种释放方法才是规范的,绝对没有问题的;

A* pA = new A2; //泄漏6字节,2个字节代表类对象数组占用的,额外4字节我们纳闷这4字节从哪里来;
delete pA; //系统报告异常,为什么报异常呢?
delete[] pA; //这种写法是最规范的,是没问题的; A:类类型

<5>为什么new / delete,new[] / delete[]要配对使用
内置类型比如 int 不用调用析构函数,所以new[]的时候没有多分配4个字节。
对于int类型,new[],delete p 或者 delete []p,效果一样。
结论:如果一个对象,使用 new[] 来分配内存,却单独使用 delete (而不是 delete[] )来。
释放内存,那么对象应该满足的几个条件:
1>对象的类型要么是内置类型或者无自定义的析构函数的类类型;
2>如果没有析构函数,则不用记录需要析构多少次。
A *pA = new A2; //如果没有析构函数,这里就分配2个字节;
delete pA; //为什么自己一提供析构函数,不用delete[]来释放new[]出来的内存就报异常呢?
1>调用一次A的析构函数而不是2次,至少是表示有内存泄漏;
2>调用operator delete(pA)释放内存。

A* pA = new A();
delete[] pA; //new的内容也千万不要用[]的来释放,否则也使系统产生不可预料的行为

结论: new/delete, new[]/delete[] 要配对使用,否则程序出错,自食恶果。

二:智能指针总述

int* p = new int();
int* q = p;
int* r = q; //只有p,q,r都不再使用了的时候,才能释放掉这段内存。

new / delete 的写法要非常的小心,防止早早的释放,也防止忘记释放,总之用好并不容易。
p裸指针:直接使用 new 返回的指针,这种指针强大,灵活,但是开发者全程负责维护,一个不小心很容易出错,一旦用错后果严重;
智能指针:解决裸指针可能带来的各种问题;
智能指针就理解成对"裸指针"进行了精心包装,给裸指针外边包了一层,包装后为我们带来了优点;
最突出的优点:智能指针能够"自动释放所指向的对象内存",大家再也不用担心自己new出来的内存忘记释放了。
建议优先选择智能指针。使用智能指针的程序更容易编写和调试。

C++标准库有四种智能指针 auto_ptr(c++98)、unique_ptr(c++11)、shared_ptr(c++11)、 weak_ptr(c++11)。
帮助我们进行动态分配对象(new出来的对象)的生命周期的管理,能够有效防止内存泄漏。
目前 auto_ptr 已经完全被 unique_ptr 取代,不要再使用 auto_ptr,C++ 表中中反对使用 auto_ptr(弃用)。
这三种智能指针都是类模板,我们可以将 new 获得的地址赋给他们。

shared_ptr:共享式指针,多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放。
weak_ptr:是辅助shared_ptr工作的。
unique_ptr:独占式指针,同一个时间内,只有一个指针能够指向该对象。当然该对象的所有权还是可以移交出去的。
你忘记delete的时候,智能指针帮助你delete,或者说你压根就不再需要自己delete,智能指针的本分(帮助你delete)。

三、shared_ptr基础

共享所有权,不是被一个shared_ptr拥有,而是被多个shared_ptr之间相互协作;
shared_ptr 有额外开销;
工作原理:引用计数,每个 shared_ptr 的拷贝都指向相同的内存,所以,只有最后一个指向该内存(对象)的 shared_ptr 指针不需要再指向该对象时,那么这个 shared_ptr 才会去析构所指向的对象。
最后一个指向该内存对象的 shared_ptr 在什么情况下回释放对象( shared_ptr 所指向的对象)呢?
1>这个shared_ptr析构的时候;
2>这个shared_ptr指向其他的对象时。
有点类似垃圾回收机制 我们从此不用担心对象何时被delete;
类模板,用到<>,<>里,就是指针可以指向的类型,后边再跟智能指针名;
格式:shared_ptr<指向的类型>智能指针名

<1>常规初始化(shared_ptr和new配合)

int main()
{
	shared_ptr<int> pi; // 指向int的智能指针,名字为pi,但目前指向为空,空指针,nullptr
	shared_ptr<int> pi(new int(100));  //pi指向一个值为100的int型数据。
	shared_ptr<int> pi2 = new int(200);  // 不行,这个智能指针是explicit,不可以进行隐式类型转换,必须用直接初始化形式。
	return 0;
}

shared_ptr pi3 = makes(130);

裸指针可以初始化shared_ptr,但不推荐。智能指针和 裸指针不能穿插使用;
int *pi = new int;
shared_ptr p1(pi);
shared_ptr p2(new int);

cout << typeid(make_shared(5, ‘a’)).name() << endl;

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

int main()
{
	shared_ptr<int> p2 = make_shared<int>(100);  //这个shared_ptr指向一个值为100的整型内存,有点类似int *p = new int(100)
	shared_ptr<string> p3 = make_shared<string>(5, 'a');  //5个字符a生成的字符串,类似于string mystr(5, 'a')
	shared_ptr<int> p4 = make_shared<int>();  //p4指向一个int,int里面保存的值为0(值初始化)
	p4 = make_shared<int>(400);  //p4指向一个新int,int里保存的是400,p4首先释放刚才指向的值为0的内存,然后指向值为400的内存
	auto p5 = make_shared<string>(5, 'a');  //用auto比较简单
	return 0;
}

ps:
shared_ptr p3 = make_shared(5, ‘a’);
//这样也会报错;
//初始化”: 无法从“initializer list”转换为“_Ty

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值