博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++Primer:第十二章:智能指针shared_ptr类
阅读量:2433 次
发布时间:2019-05-10

本文共 6037 字,大约阅读时间需要 20 分钟。

静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存称为自由空间或者堆。程序用堆来储存动态分配的对象。那些在程序运行时分配的对象,动态对象的生存期由程序来控制,当动态对象不再使用时,我们必须进行显式地销毁它们。

为了更容易且更安全地使用动态内存,避免因new和delete使用不当而导致内存泄漏,新的标准库提供了两种智能指针来管理动态对象。智能指针类似常规指针,重要的区别是它负责自动释放所指向的对象。

新标准库提供智能指针的区别在于管理底层指针的方式

shared_ptr类:允许多个指针指向同一个对象
unique_ptr类:独占所指向的对象
weak_ptr类:弱引用,指向shared_ptr所管理的对象
三种类型都定义在memory头文件中

shared_ptr类

智能指针也是模板,当创建一个智能指针时,必须提供额外信息——指针可以指向的类型

shared_ptr
p1; shared_ptr
> p2;

默认初始化的智能指针中保存着一个空指针

解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。

//shared_ptr和unique_ptr都支持的操作shared_ptr
sp; unique_ptr
up;p; //将p作为一个判断条件,若p指向一个对象,则为ture*p; //获得P所指向的对象p->mem; //(*p).memp.get() //返回p中保存的指针swap(p, q); //交换p和q的指针p.swap(q);
//shared_ptr独有的操作make_shared
(args) //返回一个shared_ptr,指向一个动态类型为T的对象,用args初始化该对象shared_ptr
p(q) //p是shared_ptr q的拷贝,此操作会递增q中的计数器,q中的指针必须能转成T*p = q //递增q的引用计数,递减p的引用计数,若p的引用计数变为0,则将其管理的原内存释放p.unique() //若p.use_count()为1,返回true;否则返回falsep.use_count() //返回与p共享对象的智能指针数量

make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向该对象的shared_ptr。

//p3指向一个值为42的int的shared_ptrshared_ptr
p3 = make_shared
(42);//p4指向一个值为“9999999999”的stringshared_ptr
p4 = make_shared
(10,'9');//p5指向一个值初始化的int,即,值为0shared_ptr
p5 = make_shared
();

make_shared<string>用其参数来构造给定类型的对象,如果我们不传递任何参数,对象就会进行值初始化。

我们通常用auto定义一个对象来保存make_shared的结果

//p6指向一个动态分配的空vector
auto p6 = make_shared
>();

shared_ptr的拷贝和赋值

我们可以认为每个shared_ptr都有一个关联的计数器,通常称为引用计数(reference count),无论何时我们拷贝一个shared_ptr,计数器都会递增。当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或者是shared_ptr被销毁时(一个局部的shared_ptr离开其作用域时),计数器就会递减。

shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr会自动销毁该对象,通过调用析构函数来实现。shared_ptr的析构函数会递减它指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占的内存。

shared_ptr还会自动释放相关联的内存

//factory返回一个shared_ptr,指向一个动态分配的对象shared_ptr
factory(T arg){
//恰当处理arg //shared_ptr负责释放内存 return make_shared
(arg);}

由于factory返回一个shared_ptr,所以我们可以确保它分配的对象会在恰当的时刻被释放。

void use_factory(T arg){
shared_ptr
p = factory(arg); //使用p}//p离开作用域,它指向的内存就会被释放掉

当p被销毁时,将递减其引用计数并检查它是否为0。在此例中,p是唯一引用factory返回的内存的对象。由于p将要销毁,p指向的这个对象也会被销毁,所占有的内存会被释放。

//如果有其他shared_ptr指向这块内存,它就不会被释放掉shared_ptr
use_factory(T arg){
shared_ptr
p = factory(arg); //使用p return p; //当返回p时,引用计数进行了递增的操作}//p离开了作用域,它指向的内存不会被释放掉

return语句为此函数的调用者返回了一个p的拷贝。拷贝一个shared_ptr会增加所管理对象的引用计数值。当p被销毁时,它指向的内存还有其他使用者。

对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被销毁掉。

使用了动态生存期的资源的类

1、程序不知道自己需要使用多少对象
2、程序不知道所需对象的准确类型
3、程序需要在多个对象之间共享数据

定义StrBlob类

定义一个管理string的类,命名为StrBlob,我们将使用vector来保存元素,为了防止当对象销毁时,vector中的数据也被销毁,我们将vector保存在动态内存中。
为了实现我们的数据共享,我们对于每一个StrBlob对象设置一个shared_ptr来管理动态分配的vector。此shared_ptr成员将记录有多少个StrBlob共享相同的vector,并在vector的最后一个使用者被销毁时释放vector。

StrBlob的拷贝、赋值和销毁

我们的StrBlob类只有一个数据成员,它是shared_ptr类型。因此,当我们拷贝、赋值或者销毁一个StrBlob对象时,它的shared_ptr成员会被拷贝、赋值和销毁。
拷贝一个shared_ptr会递增其引用计数;将一个shared_ptr赋予另一个shared_ptr会递增赋值号右侧shared_ptr的引用计数,而递减左侧shared_ptr的引用计数。如果一个shared_ptr的引用计数变为0,它所指向的对象就会被自动销毁。对于StrBlob构造函数分配的vector,当最后一个指向它的StrBlob对象被销毁时,它会随之销毁。

编写你自己的StrBlob类,包含const版本的front和back

#include 
#include
#include
#include
#include
using namespace std;class StrBlob {
public: typedef vector
::size_type size_type; StrBlob() : data(make_shared
>()) { }; //默认构造函数分配一个空的vector //接受一个initializer_list的构造函数将其参数传递给对应的vector构造函数。此构造函数通过拷贝列表中的值来初始化vector中的元素 StrBlob(initializer_list
il) :data(make_shared
>(il)) { }; size_type size() const { return data->size(); } bool empty() const { return data->empty(); } //添加和删除元素 void push_back(const string& t) { data->push_back(t); } void pop_back(); //元素访问 string& front(); const string& front() const; string& back(); const string& back() const;private: shared_ptr
> data; //如果data[i]不合法则抛出一个异常 void check(size_type i, const string& msg) const { //string描述了错误内容 if (i >= data->size()) throw out_of_range(msg); }};string& StrBlob::front() { //如果vector为空,check会抛出一个异常 check(0, "front on empty StrBlob"); return data->front();}const string& StrBlob::front() const{ //如果vector为空,check会抛出一个异常 check(0, "front on empty StrBlob"); return data->front();}string& StrBlob::back() { check(0, "back on empty StrBlob"); return data->back();}const string& StrBlob::back() const{ check(0, "back on empty StrBlob"); return data->back();}void StrBlob::pop_back() { check(0, "pop_back on empty StrBlob"); data->pop_back();}int main() { StrBlob b1; { StrBlob b2 = { "a", "an", "the" }; b1 = b2; b2.push_back("about"); cout << b2.size() << endl; } cout << b1.size() << endl; cout << b1.front() << " " << b1.back() << endl; const StrBlob b3 = b1; cout << b3.front() << " " << b3.back() << endl; b1.pop_back(); cout << b3.front() << " " << b3.back() << endl; b1.pop_back(); b1.pop_back(); cout << b1.front() << " " << b1.back() << endl; return 0;}

在这里插入图片描述

在此代码的结尾,b1和b2包含多少个元素

StrBlob b1;	{
StrBlob b2 = {
"a", "an", "the" }; b1 = b2; b2.push_back("about"); cout << b2.size() << endl; }

由于StrBlob的data成员是一个指向string的vector的shared_ptr,因此StrBlob的赋值不会拷贝vector的内容。而是多个StrBlob对象共享同一个创建在动态内存空间上的vector对象。

在代码结尾b1和b2中包含4个string

StrBlob需要const版本的push_back和pop_back吗

push_back和pop_back的语义分别是向StrBlob对象共享的vector对象添加和删除元素。我们不应该重载const版本,因为const StrBlob对象是不允许修改共享vector对象内容的

在check函数中,没有检查i是否大于0,为什么可以忽略这个检查

将check定义为私有成员函数,它只会被StrBlob的成员函数调用,而不会被用户程序所调用。因此,我们可以确保传递给i的值是符合要求的

未编写接受一个initializer_list explicit参数的构造函数,讨论这个设计的优缺点

未编写接受一个初始值列表参数的显式构造函数,意味着可以进行列表向StrBlob的隐式类型转化,在需要StrBlob的地方,可以使用列表进行替代。而且可以进行拷贝形式的初始化。令程序更为简单和方便。
这种隐式转化并不是都是好的。例如,列表中可能并非都是合法值。对于接受StrBlob的函数,传递给它一个列表,会创建一个临时的StrBlob对象,用列表对其进行初始化,然后将其传递给函数,当函数完成时,此对象被丢弃,再也无法访问。对于这种情况,我们可以定义显式的构造函数,禁止隐式类类型转换。

转载地址:http://ytxmb.baihongyu.com/

你可能感兴趣的文章
【计算机网络】—— TCP/IP篇
查看>>
【Java】【算法】——算法篇
查看>>
【Java】【数据库】知识重点——数据库篇
查看>>
【Java】知识重点——消息队列篇
查看>>
【Java】学习总结 —— HashMap之put()方法实现原理
查看>>
【计算机网络】【TCP】如何讲清楚Tcp的三次握手和四次挥手?
查看>>
【Java】-- Java核心知识点总结
查看>>
【数据库】SQL之重点知识点总结
查看>>
【计算机网络】计算机网络知识总结
查看>>
【Java】【Web】JavaWeb相关知识总结 2018-9-17
查看>>
【数据库】突破单一数据库的性能限制——数据库-分库分表总结 2018-9-20
查看>>
Slurm——作业调度处理
查看>>
Lustre 维护
查看>>
Lustre—磁盘配额测试
查看>>
SSH加密密码中的非对称式密码学
查看>>
Mac Redis安装入门教程
查看>>
python3安装教程配置配置阿里云
查看>>
Mac快捷键和实用技巧
查看>>
Git的多人协作和分支处理测试
查看>>
mysql索引回表
查看>>