C++基础知识[1]——智能指针
自动资源释放
考虑下面代码:
void f()
{
vector<string> v;
string s;
while(cin >> s)
v.push_back(s);
string *p = new string[v.size()];
// remaining processing
delete [] p;
}
在正常执行的情况下,函数中 delete 语句释放数组,函数结束时自动撤销 vector。
但如果函数内部发生异常,delete 语句执行前程序就停止执行,那么数组将不会被释放。除此之外,漏写 delete 也是很常见的情况。而不管什么异常,都保证能运行 vector 析构函数。
故一种使程序更为异常安全的的技术出现了,即"即使发生异常,程序也能正确操作"。在这个例子中即"如果发生异常,被分配的任何资源都适当地释放"。
方法一 try-catch
void f()
{
vector<string> v;
string *p = new string[v.size()];
try{
// remaining processing
// excepion occurs here
}
catch{
delete [] p;
}
// ...
}
方法二 设计资源管理类
class Resource{
private:
string *r;
public:
Resource(size_t string_size): r(new string[string_size]) {}
~Resource() {if(r) delete [] r;}
}
void f()
{
vector<string> v;
Resource res(v.size();)
// excepion occurs here
// ...
}
智能指针
对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了 operator-> 操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用 . 操作符。
访问智能指针包含的裸指针则可以用 get() 函数。由于智能指针是一个对象,所以 if(smart_ptr) 永远为真,要判断智能指针的裸指针是否为空,正确的方式是:if(smart_ptr.get())。
智能指针包含了 reset() 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。
PS:相关的智能指针在 memory 头文件中定义,在 std 命名空间中。
auto_ptr 类
auto_ptr 是一个接受类型形参的模板。
auto_ptr 对象只能保存一个指向对象的指针,并且不能用于指向动态分配的数组。
需要注意 auto_ptr 在复制和赋值的时候有特殊的行为(会将所有权转移,是破坏性操作,所以也不能存储在容器中)。
样例:
auto_ptr<string> ap1(new string("Hello"));
ap1.reset(new string("Hello")) //如果之前有指向的对象,会先删除再绑定新的
auto_ptr<string> ap2 = new string("World"); //error,必须直接初始化
auto_ptr<string> ap2 = ap1; //将所有权从ap2转给ap1,并且使ap1变为未绑定状态
ap2.reset(ap1); //同上一条
ap1.get(); //返回裸指针
ap1->c_str(); //使用对象
另外 auto_ptr 在 C++11 中已被弃用,不推荐使用。
warning: 'template<class> class std::auto_ptr' is deprecated [-Wdeprecated-declarations]
在下面的例子中,p2 夺取了 p1 的内存管理权,导致 p1 悬空,最后导致崩溃。
std::auto_ptr<string> p1(new string("Hello"));
if (p1.get()) {
std::auto_ptr<string> p2;
p2 = p1;
p2->c_str();
p1->c_str(); //crash here
}
在下面的例子中,调用 release 后对象没有被析构,反而导致了内存泄漏。
//error
std::auto_ptr<string> p1(new string("Hello"));
if (p1.get()) {
p1.release();
}
//right
std::auto_ptr<string> p1(new string("Hello"));
if (p1.get()) {
string *temp = p1.release();
delete temp; //手动释放
}
//right
std::auto_ptr<string> p1(new string("Hello"));
if (p1.get()) {
p1.reset(); //自动释放
}
shared_ptr 类
shared_ptr 允许多个指针指向同一对象。
shared_ptr<int> p1 = make_shared<int>(42);
auto p2 = make_shared<int>();
shared_ptr<int> p3(new int(23));
shared_ptr<int> p4(p1, func_d); //p4将调用func_d来代替delete
shared_ptr<int> p5(u); //u为unique_ptr,p5接管u的所有权后将u置空
if(!p1.unique()){
p1.reset(new int(243));
*p1 += val; //自己是唯一用户,可以改变对象的值
}
当 shared_ptr 被销毁时,将递减其引用计数并检查它是否为0,以决定是否销毁所指向的对象及释放内存。
shared_ptr<int> func(T arg){
shared_ptr<int> p = make_shared<int>(123);
//...
return p; //返回p时,引用计数递增
} //p离开作用域,但他指向的内存不会被释放
PS:最好不要混合使用普通指针和智能指针,原因如下:
//program 1
void process(shared_ptr<int> ptr){
//use ptr
} //离开作用域,ptr被销毁
int *x(new int(123));
process(x); //error 无法将 int* 转化为 shared_ptr<int>
process(shared_ptr<int>(x)); //合法,但内存会被释放
int a = *x; //error 未定义。此时x指向的空间已被释放,是一个空悬指针
//program 2
shared_ptr<int> p(new int(123));
int *x = p.get();
{//新程序块
shared_ptr<int>(q);
}//程序块结束,q被销毁,指向的内存也被释放
int a = *x; //error 未定义。x指向的内存已被释放
//另外整个程序块结束时,p销毁时这块内存会被二次delete
指向动态数组的 shared_ptr。与 unique_ptr 不同,shared_ptr 不直接支持管理动态数组,必须提供自定的删除器。
shared_ptr<int> sp(new int[10], [](int *p){delete[] p;});
for(size_t i = 0; i < 10; ++i)
*(sp.get() + i) = i;
unique_ptr 类
一个 unique_ptr “拥有"它所指向的对象。某个时刻只能有一个 unique_ptr 指向一个给定对象。
与 shared_ptr 不同的是,没有类似 make_shared 的标准库函数返回一个 unique_ptr。定义时必须绑定到一个 new 返回的指针上。
PS:unique_ptr 不支持普通的拷贝或赋值。
unique_ptr<string> p1(new string("Hello"));
unique_ptr<string> p2(p1); //error
unique_ptr<string> p3 = p1; //error
unique_ptr<string> p4(p1.release());
p4.reset(p1.release());
p1.release(); //error p1不会释放内存,且丢失了指针
auto p = p1.release(); //但须记得 delete(p)
//但有一个例外:可以拷贝或赋值一个将要被销毁的unique_ptr
unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone(int p){
unique_ptr<int> ret(new int(p));
return ret;
}
但向 unique_ptr 传递删除器的方式与 shared_ptr 不同。
void f(destination &d){
connection c = connect(&d);
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
}
指向动态数组的 unique_ptr。
unique_ptr<int[]> up(new int[10]{});
for(size_t i = 0; i < 10; ++i)
up[i] = i;
weak_ptr 类
weak_ptr 是一种不控制所指向对象生存期的智能指针,指向一个由 shared_ptr 管理的对象。将一个 weak_ptr 绑定到 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。
auto p = make_shared<int>(123);
weak_ptr<int> wp(p);
if(shared_ptr<int> sp = wp.lock()){
//在if中sp和wp共享对象
(*sp)++;
//...
}
参考资料:
《C++ Primer 中文版 第4版》
《C++ Primer 中文版 第5版》
C++ 智能指针详解
详解C++11智能指针