Introduction
忘了加delete?不在合适的地方加入delete?那将是一场灾难!
自动释放内存,只有类可以做到😭
于是智能指针auto_ptr
、unique_ptr
和shared_ptr
来了!
将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
再说说引用计数 :
基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次, 每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
STL一共给我们提供了四种智能指针包括 std::shared_ptr
/std::unique_ptr
/std::weak_ptr
,使用它们需要包含头文件 <memory>
.
模板auto_ptr
是C++98提供的解决方案,C+11已将将其摒弃,并提供了另外3种解决方案。
所有的智能指针类都有一个explicit构造函数 ,以指针作为参数 。比如auto_ptr
的类模板原型为:
Copy templet<class T>
class auto_ptr {
explicit auto_ptr(X* p = 0) ;
...
}
因此不能自动将指针转换为智能指针对象,必须显式调用 :
Copy shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)
shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); // allowed (explicit conversion
对全部三种智能指针都应避免 的一点:
Copy string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation); // No
全局变量在堆中,智能指针pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。
std::shared_ptr
它能够记录多少个 shared_ptr
共同指向一个对象 ,从而消除显式的调用 delete
,当引用计数变为零的时候就会将对象自动删除。
但还不够,因为使用 std::shared_ptr
仍然需要使用 new
来调用,这使得代码出现了某种程度上的不对称。
std::make_shared
就能够用来消除显式的使用 new
,所以std::make_shared
会分配创建传入参数中的对象, 并返回这个对象类型的std::shared_ptr
指针。例如:
Copy #include<iostream>
#include<memory>
void foo(std::shared_ptr<int> i) {
(*i)++;
}
int main() {
// auto pointer = new int(10); // illegal, no direct assignment
// Constructed a std::shared_ptr
auto pointer = std::make_shared<int>(10);
foo(pointer);
std::cout << *pointer << std::endl; // 11
// The shared_ptr will be destructed before leaving the scope
return 0;
}
std::shared_ptr
可以通过 get()
方法来获取原始指针,通过 reset()
来减少一个引用计数, 并通过use_count()
来查看一个对象的引用计数。例如:
Copy auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = "
<< pointer2.use_count() << std::endl; // pointer2 已 reset; 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = "
<< pointer3.use_count() << std::endl; // pointer3 已 reset; 0
std::unique_ptr
std::unique_ptr
是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:
Copy std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
make_unique
并不复杂,C++11 没有提供 std::make_unique
,可以自行实现:
Copy template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
至于为什么没有提供,C++ 标准委员会主席 Herb Sutter 在他的博客 中提到原因是因为『被他们忘记了』,这里 笑一笑😂。
既然是独占,换句话说就是不可复制。但是,我们可以利用 std::move
将其转移给其他的 unique_ptr
,例如:
Copy #include <iostream>
#include <memory>
struct Foo {
Foo() { std::cout << "Foo::Foo" << std::endl; }
~Foo() { std::cout << "Foo::~Foo" << std::endl; }
void foo() { std::cout << "Foo::foo" << std::endl; }
};
void f(const Foo &) {
std::cout << "f(const Foo&)" << std::endl;
}
int main() {
std::unique_ptr<Foo> p1(std::make_unique<Foo>());
// p1 不空, 输出
if (p1) {
p1->foo();
}
{
std::unique_ptr<Foo> p2(std::move(p1));
// p2 不空, 输出
f(*p2);
// p2 不空, 输出
if(p2) {p2->foo();}
// p1 为空, 无输出
if(p1) {p1->foo();}
p1 = std::move(p2);
// p2 为空, 无输出
if(p2) {p2->foo();}
std::cout << "p2 被销毁" << std::endl;
}
// p1 不空, 输出
if (p1) {p1->foo();}
// Foo 的实例会在离开作用域时被销毁
}
std::weak_ptr
如果你仔细思考 std::shared_ptr
就会发现依然存在着资源无法释放的问题。看下面这个例子:
Copy struct A;
struct B;
struct A {
std::shared_ptr<B> pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
struct B {
std::shared_ptr<A> pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
}
运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 a,b
,这使得 a,b
的引用计数均变为了 2,而离开作用域时,a,b
智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 a,b
对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,如图
解决这个问题的办法就是使用弱引用指针 std::weak_ptr
,std::weak_ptr
是一种弱引用(相比较而言 std::shared_ptr
就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如图
在上图中,最后一步只剩下 B,而 B 并没有任何智能指针引用它,因此这块内存资源也会被释放。
std::weak_ptr
没有 *
运算符和 ->
运算符,所以不能够对资源进行操作,它可以用于检查 std::shared_ptr
是否存在,其 expired()
方法能在资源未被释放时,会返回 false
,否则返回 true
;除此之外,它也可以用于获取指向原始对象的 std::shared_ptr
指针,其 lock()
方法在原始对象未被释放时,返回一个指向原始对象的 std::shared_ptr
指针,进而访问原始对象的资源,否则返回nullptr
。
How to choose?
如果程序要使用多个指向同一个对象的指针 ,应选择shared_ptr
。这样的情况包括:
有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr
,但不能用于unique_ptr
(编译器发出warning)和auto_ptr
(行为不确定)。如果你的编译器没有提供shared_ptr
,可使用Boost库 提供的shared_ptr
。
如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr
。
如果函数使用new
分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr
是不错的选择。这样,所有权转让给接受返回值的unique_ptr
,而该智能指针将负责调用delete
。可将unique_ptr
存储到STL容器,只要不调用将一个 unique_ptr
复制或赋给另一个 算法(如sort()
)。例如,可在程序中使用类似于下面的代码段
Copy unique_ptr<int> make_int(int n) {
return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int> &p1) {
cout << *a << ' ';
}
int main() {
...
vector<unique_ptr<int> > vp(size);
for(int i = 0; i < vp.size(); i++) {
vp[i] = make_int(rand() % 1000); // copy temporary unique_ptr
}
vp.push_back(make_int(rand() % 1000)); // ok because arg is temporary
for_each(vp.begin(), vp.end(), show); // use for_each()
...
}
其中push_back
调用没有问题,因为它返回一个临时 unique_ptr
,该unique_ptr
被赋给vp中的一个unique_ptr
。另外,如果按值而不是按引用 给show()
传递对象,for_each()
将非法,因为这将导致使用一个来自vp的非临时unique_ptr
初始化p1,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr
的企图。
在unique_ptr
为右值时,可将其赋给shared_ptr
,这与将一个unique_ptr
赋给一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()
的返回类型为unique_ptr<int>
:
Copy unique_ptr<int> pup(make_int(rand() % 1000)); // ok
shared_ptr<int> spp(pup); // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000)); // ok
模板shared_ptr
包含一个显式构造函数,可用于将右值unique_ptr
转换为shared_ptr
。shared_ptr
将接管原来归unique_ptr
所有的对象。
在满足unique_ptr
要求的条件时,也可使用auto_ptr
,但unique_ptr
是更好的选择。如果你的编译器没有unique_ptr
,可考虑使用Boost库提供的 scoped_ptr
,它与unique_ptr
类似。