本文参考C++智能指针简单剖析
内存泄露
我们知道一个对象(变量)的生命周期结束的时候, 会自动释放掉其占用的内存(例如局部变量在包含它的第一个括号结束的时候自动释放掉内存)
int main () {
{
int a = 1;
printf("%d
", a);
}
{
a = 2;
printf("%d
", a);
}
}
这样会编译错误.
但是如果这样写呢?
void func(int &o) {
int *p = new int(o);
return;
}
程序结束的时候会自动释放p
的内存, 但是由new
算符创建的匿名变量却一直留在内存中, 这就为内存泄露留下了隐患.
所以在程序结尾要加上delete p
, 释放掉p
指向的变量占用的内存.
那么能不能在指针过期的时候自动释放掉它指向的内存呢?
可惜它不是具有析构函数的类对象指针.
如果这个指针本身就是个对象, 它就可以实现在指针本身离开作用域的时候释放其指向的内存的析构函数了.
这就是auto_ptr, shared_ptr, unique_ptr
几个智能指针背后的设计思想.可以理解为这个指针本身就是一个对象, 因为它具有对象的行为.
可以将上述有问题的代码改写为这样:
void func(int &o) {
auto_ptr<int> p(new int(o));
}
这样在函数结束的时候内存就会被释放了.
智能指针
这个东西是在C++98
提供的.C++11
已经抛弃了它并且提供了另外两种解决方案.
如果没有C++11
的话就只能使用这个东西了.
不允许隐形转换
所有的智能指针都有一个explicit
构造函数, 以指针为参数.
这个东西大概可以这样写
template <class T>
class auto_ptrs {
private:
T* ptr;
public:
explicit auto_ptrs (T* p = 0): ptr(p) {}
~auto_ptrs() { delete ptr; printf("delete
"); }
};
int main () {
{
auto_ptrs<int> p (new int(1));
auto_ptr<int> ptr, pt(new int(2));
ptr = auto_ptr<int>(new int(3));
}
}
只能用于堆内存
int main() {
int a = 0;
std:: auto_ptr<int> p(&a);
}
这样写是错误的, 因为它会在main
对&a
执行delete
操作, 而a
位于栈内存, 不能被delete
.
用unique_ptr
代替auto_ptr
如果将一个指针赋值给另一个指针呢?
class Int{
int a;
public:
int J() {return a;}
Int(int _) : a(_) {}
~Int() { printf("delete
"); }
};
int main() {
auto_ptr<Int> p (new Int(123456789));
auto_ptr<Int> q;
q = p;
}
如果在p
, q
作用域结束的时候分别delete
一次, 那么就会delete
同一个变量两次, 这样是不行的.
有很多种解决方案可以解决这个问题:
- 深度复制, 就是将指针指向的内存复制一遍再让另一个指针指向它.
- 转让所有权, 由于一个对象只能由一个对象所拥有, 另一个就变空指针了, 只让拥有对象的指针删除该对象.(
auto_ptr
和unique_ptr
的策略) - 跟踪引用特定对象的智能指针数.——引用计数(
shared_ptr
的策略), 如果一个对象被引用了p次, 那么得当指针全部销毁时对象才会销毁.
但是用auto_ptr
还是有一个问题, 如果q = p
之后又调用了*p
, 对一个空指针解除引用程序是会崩溃的.
unique_ptr
的好处是如果你这样写
unique_ptr<Int> p (new Int(1));
unique_ptr<Int> q;
q = p;
你会得到一个编译错误, 原因是避免潜在的内存崩溃问题.
但是如果你把一个临时的unique_ptr
赋值给unique_ptr
却不会出现问题, 这是它聪明的地方.
例如
unique_ptr<int> func(int &a) {
unique_ptr<int> p(&a);
return p;
}
或者
unique_ptr<int> a;
a = unique_ptr<int> (new int(199));
你甚至可以将unique_ptr
显性转化为shared_ptr
, 当然对象也被接管.