现代C++(2):智能指针【2】共享指针

现代C++(2):智能指针

一级目录

二级目录

三级目录

2.共享指针:std::shared_ptr

默认使用using namespace std;

shared_ptr本质上也是C++11标准库中的一个模板类。

主要特点:

  1. 引用计数:每个 shared_ptr 实例都有一个关联的引用计数,用来跟踪有多少个 shared_ptr 正在指向同一个对象。共享指针会记录有多少个共享指针指向同一个对象,当记录为0的时候,程序会自动释放相应的内存。
  2. 所有权共享:可以通过拷贝构造函数或赋值操作符将 shared_ptr 的所有权传递给其他 shared_ptr,这会导致引用计数的增加。

另外:

  • shared_ptr 提供了一个名为 use_count() 的成员函数,用于返回当前 shared_ptr 实例所指向的对象的引用计数。这个计数值表示有多少个 shared_ptr 正在共享同一个对象的所有权。

由于共享指针使用引用计数,所以时间开销也会相对更大。

1.1创建方式
  • 创建一个空的shared_ptr<int>对象。
shared_ptr<int> p;
  • 使用std::make_shared创建对象。(推荐使用,因为效率更高也更安全)
p = make_shared(100);

make_shared会自动申请一片内存空间,然后让一个shared_ptr对象指向这片空间。它本质上是一个模板。

  • 使用new创建shared_ptr对象。
shared_pyt<int> p {new int(100)};

注意,后面跟的是大括号

1.2使用场景
#include <iostream>
#include <memory>
using namespace std;

struct Ball
{
    Ball()
    {
         cout << "Constructor." << endl;
    }
    ~Ball()
    {
         cout << "Deconstructor." << endl;
    }
    void bounce()
    {
        cout << "A ball jumps." << endl;
    }
}

int main()
{
    shared_ptr<Ball> p = make_shared<Ball>();
    cout << p.use_count() << endl;
    shared_ptr<Ball> p2 = p;
    cout << p.use_count() << p2.use_count()<< endl;
    shared_ptr<Ball> p3 = p;
    cout << p.use_count()
	<< p2.use_count()
	<< p3.use_count() << endl;
	p.reset();	//1.3有讲
	p2.reset();
	p3.reset();
}

运行结果如下:

Constructor.
1
2 2
3 3 3
Deconstructor.

首先,我们创建了三个共享指针指向同一个对象Ball,然后我们使用use_count观察有多少个共享指针指向同一个对象。最后,使用reset()来重置共享指针,使其不再指向原来的对象。当没有共享指针指向一个对象,在程序运行结束后会自动销毁,调用Ball的析构函数。

而对于原始指针而言,如果其指向的内存是动态分配的,且未被主动释放,那么即便原始指针被销毁,这块内存仍然未被释放,就会造成内存泄漏的风险。

但是共享指针的记录为0或者程序运行结束的时候,程序都会自动释放相应的内存。

这就叫做引用计数。比如有一个对象,我们创建一个共享指针指向它,那么引用计数就加1;当某一个共享指针reset()的时候,引用计数就减1。

同样,对于unique_ptr,原始指针的运算符对其同样适用,比如*->等运算符。

1.3手动销毁
  • 可以使用reset()来手动释放shared_ptr下的内存,或者释放的同时指向另一片内存。
p->reset();
p->reset(new Ball{});

第一行代码会将p下的资源全部释放,并将p设置为nullptr

第二行会将p下的资源全部释放,并将p指向新的Ball实例。

  • reset()的有参用法
shared_ptr<Ball> sp;
sp = make_shared<Ball>();
sp.reset(new Ball);

此时sp就不再指向旧的Ball,旧的Ball引用计数-1,随后sp指向新的Ball

1.4自定义释放

默认情况下,共享指针使用delete释放内存,我们也可以自定义删除函数。

void close_file(FILE* fp)
{
	if(fp == nullptr)return;
	fclose(fp);	//对文件的操作
	cout << "File closed."
	<< endl;
}

int main(){
	FILE* fp = fopen("data.txt","w”);
	shared_ptr<FILE> sfp {fp, close_file};	//对文件的操作  //用大括号初始化
//第一个参数fp是一个指向 FILE 类型的原始指针,即通过 fopen 函数打开文件后返回的文件指针。第二个参数是自定义删除函数
	if(sfp == nullptr)
	cerr << "Error opening file." << endl;
	<< "File opened." << endl;
	else cout<< "File opened."<<endl;
}
//当共享指针sfp的引用计数为0的时候,自动关闭文件

比如我们需要关闭网络连接或删除文件,则我们就可以自定义释放函数,而不是释放内存。

1.5获取原始指针

可以使用get()获取共享指针的原始指针

Ball* rp = p.get();

注意,当存在一份资源,有共享指针和原始指针同时指向它的时候,当所有共享指针被销毁,原始指针仍然存在的时候,底下的资源依旧会被释放。所以尽量要避免共享指针和原始指针混用。

1.6别名(Aliasing)

别名是共享指针的一个特殊用法

struct Bar {int i = 123;};
struct Foo { Bar bar; };
int main()
{
	shared_ptr<Foo> f = make _shared<Foo>();	//创建指向Foo的共享指针f
	cout << f.use_count()<< endl;	// prints
    
	shared_ptr<Bar> b(f, &(f->bar));	//创建类型为Bar的共享指针b
//这个初始化的语法有所不同,括号内第一个参数是共享指针f,第二个是一个指向了f的一个成员的指针:小写bar
//这个就是共享指针的别名
	cout << f.use_count()<< endl;	// prints 2
    
	cout << b->i << endl;	// prints 123
}

我们定义了两个结构体,一个为Bar,一个为Foo

当我们给这个例子中的共享指针起了别名bar后,结果是:b拥有了对f指向的对象的管理权。定义了b后,f指向的对象的引用计数就+1。只要b还存在,f指向的内存就不会被释放

但是,b的指向是f的成员,指向的是bar

从最后一句中也可以看出,当我们访问b的成员的时候,访问的是Bar的实例,而不是Foo实例。

也就是说,共享指针的别名的类型是初始化时的类型,而它的指针的类型却和它指向的类型有关。

引用计数增加和减少的是Foo,但指针指向的是Bar

共享指针的别名通常用于访问类的成员变量:我们访问某个实例的成员,但不希望调用这个成员时,实例本身被删除。所以我们增加了对这个实例本身的控制权,而访问的是成员。

1.7危险行为
int main()
{
	shared ptr<int> sp {new int};
	shared ptr<int> sp2 = sp;
	delete sp.get();
	cout << "Continued." << *sp2 << endl;	
}

如果我们使用new来创建一个共享指针,仍然可以使用delete sp.get()这样的写法,这么做会让sp的原始指针被销毁,就会造成后面语句的未定义行为

在使用共享指针的时候,就尽量不要再手动delete了。

本人才疏学浅,通篇文章有错之处在所难免,如有错误请指正,感激不尽!

以上都是群内巨佬——三色牌告诉我的,有问题可以在群里找他,我是菜鸡

群号:762514085

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值