C++的智能指针
前言
C++STL(Standard Template Library)一共提供了四种指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,其中auto_ptr是c++98提供的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr。
1、unique_ptr
独占的指针,只可以自己使用,它指向的对象只可以他一个人使用,可以使用move将使用权转移,如:
#include <iostream>
#include <memory>
using namespace std;
int main(){
auto ptr1=make_unique<string> ("12345");
cout<<*ptr1<<endl;
auto ptr2=std::move(ptr1);
// cout<<*ptr1<<endl;
cout<<*ptr2<<endl;
return 0;
}
// 12345
// 12345
创建智能指针的方法:通过构造函数指定、通过 reset 方法重新指定、通过 release 方法释放所有权、通过移动语义转移所有权(move),unique_ptr 还可能没有对象,这种情况被称为 empty。
#include <iostream>
#include <memory>
using namespace std;
int main(){
unique_ptr<int> p1;
p1.reset(new int(123));
cout<<*p1<<endl;
unique_ptr<int> p2(new int(1234));
cout<<*p2<<endl;
int *p3=p1.release();
cout<<*p3<<endl;
unique_ptr<int> p4=move(p2);
cout<<*p4<<endl;
return 0;
}
// 123
// 1234
// 123
// 1234
2、auto_ptr
引入问题
auto_ptr< string> p1(new string ("string1");
auto_ptr<string> p2;
p2=p1;
如果上面的指针是普通的指针,那么就会面临一个问题,就是delete的时候会删除有两次,解决方案有多种:
1、重载复制运算符,将其定义为深复制,这样他们俩就会指向不同的地方,缺点是会浪费空间。
2、建立所有全概念。将指针定义为只可以有一个对象拥有,赋值运算符直接将所有权转移。这就是用于 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更严格。
3、创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加 1,而指针过期时,计数将减 1,。当减为 0 时才调用 delete。这是 shared_ptr 采用的策略。
3、shared_ptr
shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在 memory 文件中,命名空间为 std。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。像 shared_ptr 这种智能指针,《Effective C++》称之为“引用计数型智能指针”(reference-counting smart pointer,RCSP)。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销: (1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针; (2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样; (3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。
可以使用辅助类来实现该智能指针,它的具体做法如下: (a)当创建智能指针类的新对象时,初始化指针,并将引用计数设置为1; (b)当能智能指针类对象作为另一个对象的副本时,拷贝构造函数复制副本的指向辅助类对象的指针,并增加辅助类对象对基础类对象的引用计数(加1); (c)使用赋值操作符对一个智能指针类对象进行赋值时,处理复杂一点:先使左操作数的引用计数减 1(为何减 1:因为指针已经指向别的地方),如果减1后引用计数为 0,则释放指针所指对象内存。然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象); (d)完成析构函数:调用析构函数时,析构函数先使引用计数减 1,如果减至 0 则 delete 对象。
接口:
class Point {
private:
int x, y;
public:
Point(int xVal = 0, int yVal = 0) :x(xVal), y(yVal) {}
int getX() const { return x; }
int getY() const { return y; }
void setX(int xVal) { x = xVal; }
void setY(int yVal) { y = yVal; }
};
实现:
class SmartPtr {
public:
//构造函数
SmartPtr() { rp = nullptr; }
SmartPtr(Point *ptr):rp(new RefPtr(ptr)) {}
SmartPtr(const SmartPtr &sp):rp(sp.rp) {
++rp->count;
cout << "in copy constructor" <<endl;
}
// 重载赋值运算符
SmartPtr& operator=(const SmartPtr& rhs) {
++rhs.rp->count;
if (rp != nullptr && --rp->count == 0) {
delete rp;
}
rp = rhs.rp;
cout << "in assignment operator" << endl;
return *this;
}
// 重载->操作符
Point* operator->() {
return rp->p;
}
// 重载*操作符
Point& operator*() {
return *(rp->p);
}
~SmartPtr() {
if (--rp->count == 0) delete rp;
else cout << "还有" << rp->count << "个指针指向基础对象" << endl;
}
private:
RefPtr* rp;
};
4、weak_ptr
这个智能指针只能算是一个辅助类的指针,没有重载 operator 和 operator-> ,因此取名为 weak,表明其是功能较弱的智能指针。*它的最大作用在于协助 shared_ptr 工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 weak_ptr 只对 shared_ptr 进行引用,而不改变其引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。
解决循环引用的问题,用法:
weak_ptr<T> w; //创建空 weak_ptr,可以指向类型为 T 的对象
weak_ptr<T> w(sp); //与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型
w=p; //p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象
w.reset(); //将 w 置空
w.use_count(); //返回与 w 共享对象的 shared_ptr 的数量
w.expired(); //若 w.use_count() 为 0,返回 true,否则返回 false
w.lock(); //如果 expired() 为 true,返回一个空 shared_ptr,否则返回非空 shared_ptr
weak_ptr 对象引用资源时不会增加引用计数,但是它能够通过 lock() 方法来判断它所管理的资源是否被释放。