16.1 C++智能指针-new/delete探秘
16.2 C++智能指针-shared_ptr
16.3 C++智能指针-weak_ptr
16.4 C++智能指针-shared_ptr使用场景、陷阱、性能分析与使用建议
16.5 C++智能指针-unique_ptr
智能指针的引入正是为了防止无意之间写出有内存泄漏的程序。内存释放的工作将交给智能指针来完成,在很大程度上能够避免程序代码中的内存泄漏。
1.new/delete探秘
-
new/delete是什么
new/delete和malloc/free最明显的区别之一就是使用new生成一个类对象时系统会调用该类的构造函数,使用delete删除一个类对象时系统会调用该类的析构函数(释放函数)。既然有调用构造函数和析构函数的能力,这就意味着new和delete具备针对堆所分配的内存进行初始化(把初始化代码放在类的构造函数中)和释放(把释放相关的代码放在类的析构函数中)的能力,而这些能力是malloc和free所不具备的。
-
operator new()和operator delete()
new运算符做了两件事:①分配内存;②调用构造函数初始化该内存。
delete运算符也做了两件事:①调用析构函数;②释放内存。
-
new如何记录分配的内存大小供delete使用
不同编译器的new内部都有不同的实现方式
-
申请和释放一个数组
为数组动态分配内存时,往往需要用到[],如new[…],而释放数组时,往往也要用到[],如delete[…],这意味着,往往new[]和delete[]要配套使用。
-
为什么new/delete、new[]/delete[]要配对使用
如果一个对象,是用new[]分配内存,而却用delete(而不是delete[])来释放内存,那么这个对象满足的条件是:对象的类型是内置类型(如int类型)或者是无自定义析构函数的类类型。
A * pA = new A[2]; //这里不再分配6字节,而是2字节
delete pA; //这里不再出现内存泄漏,也不再报异常
那事情反过来看,如果类A书写了自己的析构函数,则用new[]为对象数组分配内存,而用单独的delete来释放内存,就会报异常。
A * pA = new A[2]; //这里分配6字节
delete pA; //这里报异常 为什么异常?
报异常的原因是,代码行“delete pA;”做了两件事:
(1)调用一次类A的析构函数。new的时候创建的是两个对象,调用的是两次构造函数,而释放的时候调用的是一次析构函数,虽然不致命,但也存在后遗症(如类A的构造函数中如果分配了内存,指望在析构函数中释放内存,那么如果少执行一次析构函数,就会直接导致内存的泄漏)
(2)调用“operator delete(pA);”来释放内存。系统所报的异常,其实就是执行这行代码的调用导致的。就是因为多分配这4字节的问题导致释放的内存空间错乱。例如,明明应该释放一个0x00000012作为开始地址的内存,因为内存空间错乱,导致释放了0x00000016作为开始地址的内存,从而导致出现异常。
所以,new/delete、new []/delete[]要配对使用,否则程序运行出错。
#include <iostream>
#include <vector>
using namespace std; //后面再使用诸如std::cout时就可以简写成cout了;
class A
{
public:
A()
{
cout << "A" << endl;
}
int m_i;
};
int main()
{
{
int* pointi = new int; //pointi指向一个int对象
string* mystr = new string;
}
{
int* pointi = new int(100); //跟踪调试,指针指向的值变成了100
string* mystr2 = new string(5, 'a'); //生成5个a的字符串,调用的是符合给进去的参数的string构造函数来构造出合适的字符串内容
vector<int>* pointv = new vector<int>{ 1,2,3,4,5 }; //一个容器对象,里面有5个元素,分别是1,2,3,4,5
}
{
string* mystr2 = new string(); //“值初始化”,感觉和string *mystr = new string;效果一样,总之最终字符串内容为空("")
int* pointi3 = new int(); //值被初始化为0, 这个()加与不加确实不一样,只有加了()值才会被初始化为0
}
{
A* pa1 = new A;
A* pa2 = new A();
}
{
string* mystr2 = new string(5, 'a');
const char* p = mystr2->c_str();
auto mystr3 = new auto(mystr2); //注意这种写法,mystr3会被推断成string *类型
//string** mystr3 = new (string *)(mystr2);
delete mystr2;
delete mystr3;
}
{
const int* pointci = new const int(200); //new后面这个const可以不写,似乎都差不多;当然const对象不能修改其值
//*pointci = 300; //不合法
cout << "断点放到这里方便观察" << endl;
delete pointci; //new的内存不能忘记释放
}
{
char* p = nullptr;
delete p;
delete p;
}
{
int i;
int* p = &i;
//delete p; //不是new出来的不能delete,否则编译不报错,但执行时会出现异常
}
{
int* p = new int();
int* p2 = p;
delete p2; //没问题
//delete p; //异常,因为p和p2指向同一块内存,该内存已经通过delete p2释放了,所以两个指针指向同一块内存这种,也比较麻烦,释放了p就不能再释放p2,释放了p2就不能再释放p,换句话说,如果释放了p2,也就不能再使用p
}
{
const int* pci = new const int(300);
delete pci; //可以delete const对象
}
{
int* pci = new int(300);
delete pci; //可以delete const对象
*pci = 900;
}
return 0;
}