在C++中使用指针访问内存是一种很方便的方式,但动态内存管理容易出现两种问题
一、忘记释放内存(比如异常抛出导致直接退出函数而没执行后面的delete操作)
二、当前指针所指向的内存被释放了,该指针引用非法内存
因此为了解决这个问题,C++引入了智能指针概念,智能指针的作用是在当前代码块结束时自动释放内存
四种智能指针
auto_ptr
shared_ptr
weak_ptr
unique_ptr
此文不讨论auto_ptr(已经过时)
shared_ptr
多个shared_ptr指向同一处资源,当所有shared_ptr都全部释放时,该处资源才释放。每多一个就会计数+1,当计数为0时代表是最后一个指针便释放
#include <iostream>
#include <memory>
using namespace std;
class String {
public:
String(string s): ss(s) {
}
~String() {
cout << "i am released" << "\n";
}
void show(string tmp) {
cout << tmp << ':' << ss << '\n';
}
private:
string ss;
};
int main() {
shared_ptr <String> s (new String("hellow world"));
shared_ptr <String> s1 (s);
shared_ptr <String> s2 (s);
s->show("s");
s1->show("s1");
s2->show("s2");
return 0;
}
运行结果
s:hellow world
s1:hellow world
s2:hellow world
i am released
最后只被释放了一次
shared_ptr的缺陷
如果类的两个对象互相引用会出现计数不正常
#include <iostream>
#include <memory>
using namespace std;
class String {
public:
String(string s): ss(s) {
}
~String() {
cout << "i am released" << "\n";
}
void show(string tmp) {
cout << tmp << ':' << ss << '\n';
}
void setptr(shared_ptr <String> &tmp) {
qq = tmp;
}
private:
string ss;
shared_ptr <String> qq;
};
int main() {
shared_ptr <String> s1 (new String("hellow world"));
shared_ptr <String> s2 (new String("hellow world"));
s1->setptr(s2);
s2->setptr(s1);
return 0;
}
运行结果是空这代表s1和s2没有被释放
这是因为在s1中qq指向s2,s2中qq指向s1
s1计数为2,s2计数为2
当释放s1时,s1计数减一为1
当释放s2时,s2计数减一为1
但其中的qq没有被释放,所以计数没减到0两者内存都没被释放
为了解决这个问题,因引入弱指针weak_ptr概念
weak_ptr
weak_ptr通常不单独使用,一般用于查看对应的shared_ptr的信息。weak_ptr没有重载*,->
等指针运算符,因此不能直接使用weak_ptr访问其指向的内存内容,其更多是作为一个观测者来使用
weak_ptr常用成员函数
reset():释放被管理对象所有权
swap():交换被管理对象
use_count():返回被管理对象的被shared_ptr指向的个数
expired():检查被管理对象是否被删除
lock():返回被管理对象的shared_ptr
注意:weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock
函数。
将上面代码的类中shared_ptr<String> qq改为weak_ptr<String> qq后输出
i am released
i am released
可见weak_ptr不会增加计数
下面是weak_ptr的简单使用
#include <iostream>
#include <memory>
using namespace std;
class String {
public:
String(string s): ss(s) {
}
~String() {
cout << "i am released" << "\n";
}
void show(string tmp) {
cout << tmp << ':' << ss << '\n';
}
private:
string ss;
};
int main() {
shared_ptr <String> s1 (new String("hellow world"));
shared_ptr <String> s2 (s1);
weak_ptr<String> s_monitor = s1;
cout << "s_monitor.use_count()=" << s_monitor.use_count() << '\n';
cout << "use s_monitor.lock():";
s_monitor.lock()->show("s_monitor");
return 0;
}
运行结果
s_monitor.use_count()=2
use s_monitor.lock():s_monitor:hellow world
i am released
unique_ptr
与shared_ptr不同的是unique_ptr只能一个指针指向一个内存
unique_ptr <String> s1(new String("hellow world"));
unique_ptr <String> s2;
s2 = s1;//错误
但如果s2接受的是一个临时右值又是可以的
unique_ptr <String> GetUnique_ptr(string s) {
unique_ptr<String> tmp(new String(s));
return tmp;
}
...
unique_ptr <String> s2 = GetUnique_ptr("hellow world");
这里 GetUnique_ptr返回了一个临时的unique_ptr<String>指针,s2接管了原来的对象,而在GetUnique_ptr函数中tmp指针被销毁(不是内存),这是合法的,但如果unique_ptr指针要存在一段时间就不可以这样做,如果非要这么做并保留原来的unique_ptr指针,应该使用move函数更加安全
unique_ptr <String> s1(new String("hellow world"));
unique_ptr <String> s2 = move(s1);
s1 = unique_ptr <String> (new String("hellow world"));
其他的一些琐碎的知识
一、所有智能指针应该避免使用非堆内存
String s("hellow world");
unique_ptr <String> s1(&s);
s1过期时就会释放s存在栈中的内容,这是错误的
二、尽量不要将普通指针和智能指针混用,如果两者指向同一块内存就会释放多遍内存
三、unique_ptr可以用数组,而shared_ptr不可以
例如unique_ptr<int []>s(new int(10))//自动使用delete[]
四、在unique_ptr作为右值时可以赋给shared_ptr,shared_ptr将接管unique_ptr所有对象
unique_ptr <String> GetUnique_ptr(string s) {
unique_ptr<String> tmp(new String(s));
return tmp;
}
...
unique_ptr <String> s1(new String("hellow world"));
shared_ptr <String> s2(s1)//错误的,s1为左值
shared_ptr <String> s2(GetUnique_ptr("test!"));//合法的。因为函数返回的是unique_ptr右值