1、存在的问题
c++ 把内存的控制权对程序员开放,让程序显式的控制内存,这样能够快速的定位到占用的内存,完成释放的工作。但是此举经常会引发一些问题,比如忘记释放内存。由于内存没有得到及时的回收、重复利用,所以在一些c++程序中,常会遇到程序突然退出、占用内存越来越多,最后不得不选择重启来恢复。造成这些现象的原因可以归纳为下面几种情况.
1.1 野指针
出现野指针的有几个地方 :
指针声明而未初始化,此时指针的将会随机指向;
内存已经被释放、但指针仍然指向它。这时内存有可能被系统重新分配给程序使用,从而会导致无法估计的错误;
#include <iostream>
using namespace std;
int mian(){
//1. 声明未初始化
int *p1 ;
cout << "打印p1: " << *p1 << endl;
//2. 内存释放后,并没有置空 nullptr
int *p = new int(55);
cout << "释放前打印 : " << *p << endl;
delete p ;
cout << "释放后打印 : " << *p << endl;
return 0 ;
}
1.2 重复释放
程序试图释放已经释放过的内存,或者释放已经被重新分配过的内存,就会导致重复释放错误.
int main(){
int *p = new int(4);
//重复释放
delete p;
delete p;
return 0 ;
}
1.3 内存泄漏
不再使用的内存,并没有释放,或者忘记释放,导致内存没有得到回收利用。 忘记调用delete
int main(){
int *p = new int(4);
//后面忘记调用delete p;
return 0 ;
}
2、智能指针
为了解决普通指针的隐患问题,c++在98版本开始追加了智能指针的概念,并在后续的11版本中得到了提升
c++11标准用 unique_ptr | shared_ptr | weak_ptr 等指针来自动回收堆中分配的内存。智能指针的用法和原始指针用法一样,只是它多了些释放回收的机制罢了。
2.1 unique_ptr
unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权
也就是只有这个指针能够访问这片空间,不允许拷贝,但是允许移动(转让所有权)
#include<iostream>
#include <memory>
using namespace std;
int main() {
// 1. 创建unique_ptr对象,包装一个int类型指针
unique_ptr<int> p(new int(10));
// 2. 无法进行拷贝。编译错误
//unique_ptr<int> p2 = p;
cout << "-----1----" << *p << endl;
// 3. 可以移动指针到p3. 则p不再拥有指针的控制权 p3 现在是唯一指针
unique_ptr<int> p3 = move(p);
cout << "-----2----" << *p3 << endl;
// p 现在已经无法取值了。
// cout << "-----3----" << *p << endl;
// 可以使用reset显式释放内存。
p3.reset();
// 重新绑定新的指针
p3.reset(new int(6));
// 获取到包装的int类型指针
int *p4 = p3.get();
// 输出6
cout << "-----4----" << "指针指向的值是:" << *p4 << endl;
cout << "-----5----" << "*p3值是:" << *p3 << endl;
return 0;
}
2.2 shared_ptr
shared_ptr : 允许多个智能指针共享同一块内存,由于并不是唯一指针,所以为了保证最后的释放回收,采用了计数处理,每一次的指向计数 + 1 , 每一次的reset会导致计数 -1 ,直到最终为0 ,内存才会最终被释放掉。 可以使用use_cout 来查看目前的指针个数
#include <iostream>
#include <memory>
using namespace std;
class Stu {
public:
Stu() {
cout << "执行构造函数" << endl;
}
~Stu() {
cout << "执行析构函数" << endl;
}
};
int main() {
shared_ptr<Stu> s1(new Stu());
cout << " ---1--- = " << s1.use_count() << endl; // 查看指向计数
shared_ptr<Stu> s2 = s1;
cout << " ---2--- = " << s1.use_count() << endl; // 查看指向计数
shared_ptr<Stu> s3 = s1;
cout << " ---3--- = " << s1.use_count() << endl; // 查看指向计数
s1.reset();
cout << " ---4--- = " << s3.use_count() << endl; // 查看指向计数
s2.reset();
cout << " ---5--- = " << s3.use_count() << endl; // 查看指向计数
s3.reset(); // 至此全部解除指向 计数为0 。 会执行stu的析构函数
cout << " ---6--- = " << s3.use_count() << endl; // 查看指向计数
return 0;
}
问题
对于引用计数法实现的计数,总是避免不了循环引用(或环形引用)的问题,即我中有你,你中有我,shared_ptr也不例外。 下面的例子就是,这是因为f和s内部的智能指针互相指向了对方,导致自己的引用计数一直为1,所以没有进行析构,这就造成了内存泄漏
#include <iostream>
#include <memory>
using namespace std;
class Son;
class Father {
private:
shared_ptr<Son> son;
public:
Father() {
cout << "father 构造" << endl;
}
~Father() {
cout << "father 析构" << endl;
}
void setSon(shared_ptr<Son> son) {
this->son = son;
}
};
class Son {
private:
shared_ptr<Father> father;
public:
Son() {
cout << "son 构造" << endl;
}
~Son() {
cout << "son 析构" << endl;
}
void setFather(shared_ptr<Father> father) {
this->father = father;
}
};
int main() {
shared_ptr<Father> f(new Father());
shared_ptr<Son> s(new Son());
f->setSon(s);
s->setFather(f);
}
解决办法
定义对象的时候,用shared_ptr指针;引用对象的地方,用weak_ptr指针
2.3 weak_ptr
#include <iostream>
#include <memory>
using namespace std;
class Son;
class Father {
private:
weak_ptr<Son> son;
public:
Father() {
cout << "father 构造" << endl;
}
~Father() {
cout << "father 析构" << endl;
}
void setSon(shared_ptr<Son> son) {
this->son = son;
}
};
class Son {
private:
weak_ptr<Father> father;
public:
Son() {
cout << "son 构造" << endl;
}
~Son() {
cout << "son 析构" << endl;
}
void setFather(shared_ptr<Father> father) {
this->father = father;
}
};
int main() {
shared_ptr<Father> f(new Father());
shared_ptr<Son> s(new Son());
f->setSon(s);
s->setFather(f);
}