C++智能指针
Effective C++ Item 13,以对象管理资源,提到了两个关键点:
- 获得资源后立即放进管理对象
- 管理对象运用析构函数确保资源被释放
智能指针就是为了迎合以上两点,作用就是 便于内存管理,确保程序不存在内存和资源泄露,并且是异常安全的。
C++存有四种智能指针
-
auto_ptr
auto_ptr , C++11已经弃用。auto_ptr管理的资源 绝对没有一个以上的 auto_ptr 指向这份资源,也就是说 复制一个 auto_ptr 时,被复制的 auto_ptr 会指向为空。
auto_ptr<int> p1(new int(10)); auto_ptr<int> p2 = p1; cout << p1.get() << endl; // output: 0, 即 p1 = nullptr
- unique_ptr
unique_ptr , auto_ptr 的替代品,与 auto_ptr 一样拥有唯一拥有权的特性,不过,与 auto_ptr 不一样的是,unique_ptr 没有复制构造函数,这样可以防止使用者不小心转移了拥有权。如果想转移拥有权,需要显示调用 move 函数。所以,函数传参值传递的时候也需要显示调用 move 函数。但是,函数的返回值已经进行了 move ,所以不需要显示调用 move 函数。
unique_ptr<int> fun(unique_ptr<int> p)
{
return p;
}
int main()
{
shared_ptr<A> arrayObj(new A[5], [](A *p) {
delete[] p;
});
unique_ptr<int> p(new int (10));
// unique_ptr<int> p1 = p; //! error: use of deleted function...
unique_ptr<int> p2 = move(p);
unique_ptr<int> p3 = fun(move(p2));
return 0;
}
unique_ptr 的 release() 方法虽然不会销毁 unique_ptr 指向的资源,但是调用 release() 方法后,unique_ptr 就会从管理资源的责任重解脱出来,也就是说,此时必须要手动销毁资源。
- share_ptr
share_ptr , 采用引用计数的智能指针,多个 share_ptr 可以共享资源,如果一个 share_ptr 放弃资源的”所有权”,其他 share_ptr 对资源的引用并不会发生变化。只有在引用计数为0的时候,share_ptr 才会释放资源。
void fun(shared_ptr<int> p)
{
cout << "ref count:" << p.use_count() << endl; // 函数传参,值传递,所以引用数+1
}
int main ()
{
shared_ptr<int> p1(new int(10));
cout << "ref count:" << p1.use_count() << endl;
shared_ptr<int> p2 = p1;
cout << "ref count:" << p1.use_count() << endl;
fun(p1);
cout << "ref count:" << p1.use_count() << endl; // 函数退出,参数的 share_ptr 自动销毁,引用数-1
p2.reset();
cout << "ref count:" << p1.use_count() << endl;
p3.reset();
cout << "ref count:" << p1.use_count() << endl; // 此时只有p1指向资源,只有一份引用
return 0;
}
output:
ref count:1
ref count:2
ref count:3 // 函数传参,值传递,所以引用数+1
ref count:2 // 函数退出,参数的share_ptr自动销毁,引用数-1
ref count:2
ref count:1 // 此时只有p1指向资源,只有一份引用
使用 share_ptr 的时候要注意避免使用 get() 方法获取和使用裸指针,因为使用裸指针的时候,可能会不经意间手动 delete 了资源对象,所以,当 share_ptr 去试图销毁管理的资源时,导致ACCESS VIOLATION
shared_ptr<int> a(new int(10)); int * pa = a.get(); delete pa; //! ACCESS VIOLATION
share_ptr 将调用 delete 释放内存,所以当 share_ptr 指向数组对象的时候,传递给 share_ptr 一个自定义的 delete 方法。可通过 lambda 表达式完成。
class A {
public:
A()
{
cout<<"constructor"<<endl;
}
~A()
{
cout << "destructor"<<endl;
}
};
int main()
{
shared_ptr<A> arrayObj(new A[5], [](A *p) {
delete[] p;
});
return 0;
}
share_ptr 已经足够好足够用了,但是还是可能出现问题,比如下面的代码,就会出现 环形引用问题,如下代码,A和B的对象各有一份,且引用数为2,程序结束时,即使会销毁智能指针,但是由于引用数不为0,对象并不能被回收,造成内存泄漏。
class B;
class A {
public:
shared_ptr<B> p_b;
};
class B {
public:
shared_ptr<A> p_a;
};
int main()
{
shared_ptr<A> a(new A);
shared_ptr<B> b(new B);
a->p_b = b;
b->p_a = a;
return 0;
}
- weak_ptr
要解决环形引用问题并没有好的解决办法,除了在编码过程中注意,也可以在可能出现环形引用的地方使用 weak_ptr 。 weak_ptr 比较特殊,它可以指向 share_ptr 指向的资源,但是却不拥有该资源。可以通过 weak_ptr 对象的成员函数 lock() 返回指向该资源的一个 share_ptr 对象。如果 weak_ptr 指向的资源已经无效时,会返回一个空值 nullptr 。所以使用 lock() 时,要注意返回的 share_ptr 是否有效。由于 weak_ptr 是指向 share_ptr 指向的资源,所以 weak_ptr 不能独立存在。
void fun(weak_ptr<int> & wp)
{
shared_ptr<int> p = wp.lock();
if (p != nullptr) {
cout << *p << endl;
} else {
cout << "Pointer is invalid." << endl;
}
}
int main()
{
shared_ptr<int> p1(new int(10));
shared_ptr<int> p2 = p1;
weak_ptr<int> wp = p1;
cout << "ref count: " << p1.use_count() << endl;
fun(wp);
p1.reset();
fun(wp);
p2.reset();
fun(wp);
return 0;
}
/**
output:
ref count: 2
10
10
Pointer is invalid.
**/
上面提到的 环形引用 问题可以采取如下方式解决。此时程序退出后,对象会被正确回收。
class B;
class A {
public:
weak_ptr<B> p_b;
};
class B {
public:
weak_ptr<A> p_a;
};
int main()
{
shared_ptr<A> a(new A);
shared_ptr<B> b(new B);
a->p_b = b;
b->p_a = a;
return 0;
}
goudan-er SHARE · CPP
C++