C++的智能指针


目录

前言

1、unique_ptr

2、auto_ptr

引入问题

3、shared_ptr

接口:

4、weak_ptr

参考文章:

前言

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() 方法来判断它所管理的资源是否被释放。

参考文章:

C++ STL 四种智能指针_恋喵大鲤鱼的博客-CSDN博客_智能指针


文章作者: AllenMirac
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AllenMirac !
  目录