• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2019-12-06 21:52 Aet 隐藏边栏 |   抢沙发  20 
文章评分 3 次,平均分 5.0

shared_ptr类

简述内存的分配:
静态内存用来保存局部的static对象,类static数据成员,以及定义在函数之外的变量。
栈内存用于保存定义在函数内的非static对象。
分配在静态内存或者栈内存中的对象,由编译器自动创建或销毁。
对于栈对象,仅在其定义的程序块允许时才存在;而static对象则在使用之前分配,程序结束时销毁。
另外,
除了静态内存和栈内存,每个程序还拥有一个内存池。
这部分内存被称作自由空间或堆。
程序用堆来存储动态分配的对象。
也就是说,我们在程序运行期间分配的对象,这些对象的生命周期是由程序员来控制的。换句话说,当动态对象不再被使用时,就应该在代码里显示地被销毁掉。

所以,第二篇结尾处的智能指针,以及现在这个地方的shared_ptr类,都是关于那些我们动态分配的对象(或者说内存)的管理的。
在C++当中,
我们使用new这个关键字,在动态内存中为我们的对象分配一块空间,并返回一个指向该对象的指针。
同样的,我们可以通过给delete这个关键字一个动态对象的指针,去销毁这个对象,并释放与之关联的内存。

但是,对于由我们来操控分配的这些内存,有时候却是保证不了在正确的时间内释放掉的。
而这个正确的时间内,可能意味着当我们错过它时,再去释放的时候,就会产生非法内存的指针(假如此时还有指针引用了这块内存),或者,我们直接忘记了释放这块内存,那么很显示就导致了内存泄漏

智能指针的话,它能帮助我们在正确的时间内,去释放为对应的动态对象分配的内存空间。

而关于C++11标准中的shared_ptr

当指向动态对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此动态对象。
它是通过它的析构函数完成这个销毁工作的。
比如:
比如string,string的构造函数会分配内存用来保存构成string的字符。而string的析构函数就负责释放这些内存。

当动态对象不再被使用时,shared_ptr就会自动释放动态对象。

动态分配对象的列表初始化

auto和动态分配

unique_ptr类

一个unique_ptr拥有它所指向的对象。
(某个时刻只能有一个一个unique_ptr指向一个给定对象)
当unique_ptr被销毁时,它所指向的对象也被销毁。

unique_ptr u1 空unique_ptr,可以指向类型为T的对象。u1会使用delete释放它的指针
unique_ptr<T,D> u2 u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr<T,D> u(d) 空unique_ptr指针,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr 释放u指向的对象,将u置为空
u.release() u放弃对指针的控制权,返回指针,并将u置空
u.reset() 释放u指向的对象
u.reset(q) 如果提供了内置指针q,令u指向这个对象,否则将u置为空
u.reset(nullptr)

调用release会切断unique_ptr和它原来管理的对象之间的联系。

注:不能拷贝unique_ptr的规则有一个例外(我们可以拷贝或赋值一个将要被销毁的unique_ptr),如下:

对于这两段代码,编译器都知道返回的对象将要被销毁,在此情况下,编译器执行一种特殊的“拷贝”

weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。
将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使此时还有weak_ptr指向对象,对象也还是会被释放的。

由于对象可能不存在,所以我们不能使用weak_ptr直接访问对象,在使用之前,必须先调用lock,判断一下weak_ptr指向的对象是否仍存在。如果确实存在,lock会返回一个指向共享对象的shared_ptr。

allocator类

new有一些灵活性上的局限,其中一方面的原因是它将内存分配和对象构造组合在了一起。类似的,delete将对象析构和内存释放组合在了一起。
而当分配一大块内存时,我们通常希望,这块内存按需求构造对象。在这种情况下,我们希望将内存分配和对象构造分离开来。(也就是说,我们可以分配大块内存,但只在需要的时候才真的执行对象创建操作)

allocator可以帮助我们将内存分配和对象构造分离开来。
它提供的是一种类型感知的内存分配方法,它分配的内存是原始的,未构造的。

allocator a 定义一个名为a的allocator对象,它可以为类型为T的对象分配内存
a.allocate(n) 分配一段原始的,未构造的内存,保存n个类型为T的对象
a.deallocate(p,n) 释放从T*类型指针p中地址开始的内存,这块内存保存了n个类型为T的对象。p必须是一个先前由allocate返回的指针,且n必须是创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destory
a.construct(p,args) p必须是一个类型为T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象。
a.destroy(p) p为T*类型的指针,对p指向的对象执行析构函数

allocator分配的内存是未构造的,我们需要在此内存中构造对象。

在C++11标准中,construct成员函数接受一个指针和0个或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象。

=default

用于拷贝控制成员
我们可以将拷贝成员定义为=default来显示地要求编译器生成合成的版本
注意:我们只能对具有合成版本的成员函数使用=default(默认构造函数或拷贝控制成员)

=delete

用于阻止拷贝类对象
在C++11标准下,我们可以将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝。
注意:析构函数不能是删除的成员

用移动类对象代替拷贝类对象

C++11标准新引入了两种机制,我们可以避免string的拷贝。
移动构造
有一些标准库类,都定义了所谓的“移动构造函数”。

std::move
定义在utility头文件中

  • 当reallocate在新内存中构造string时,它必须调用move来表示希望使用string的移动构造函数。如果它漏掉了move,那它会调用string的拷贝构造函数。
  • 我们通常不为move提供一个using说明。当我们调用move时,直接调用std::move而不是move。
  • move函数可告诉编译器:我们有一个左值,但我们希望像一个右值一样的处理它。我们必须认识到,调用move就意味着承诺:除了对一个rr1(见下面右值引用代码)赋值或销毁它外,我们将不再使用它。

    右值引用

    为了支持移动操作,C++11标准引入了一种新的引用类型-右值引用。
    所谓右值引用就是必须绑定到右值的引用。
    我们通过&&来获取右值引用。
    右值引用只能绑定到一个将要销毁的对象,因此,我们可以“自由”的将右值引用的资源“移动到另一个对象中。
    一般而言,一个左值表达式表示的是一个对象的身份。而一个右值表达式表示的是对象的值。
    左值持久,右值短暂。

移动构造函数&&移动赋值运算符

上面有讲述到,有一些标准库类(诸如string等),都定义了所谓的“移动构造函数”。
如果,我们自己定义的类,也同时支持移动和拷贝,那么,结合这些实现了”移动构造“的标准库类,我们将会从中获益。
为了让我们自己的类支持移动操作,我们需要给它定义移动构造函数移动赋值运算符
虽然这两个成员对应类似的”拷贝“操作,但实质上,它们是从给定对象”窃取“资源,而不是真的”拷贝“资源。
类似拷贝构造函数,移动构造函数的第一个参数也是该类类型的一个引用。但是,不同之处,就是,移动构造函数用到这个引用,它必须是一个右值引用
另外,除了完成资源的移动,移动构造函数还必须确保,源对象在被移动资源后,得处于一种状态,即销毁这个源对象是无害的,这样的一种状态。
接下来一点,就很好理解了,一旦资源完成了被移动,那么,源对象就不能再指向这个被移动的资源了(因为这些资源的所有权已经属于新创建的对象了嘛)。

如下方这个类,根据它的环境实现了移动构造和移动赋值运算符

移动迭代器

C++11标准定义一种移动迭代器适配器
一个移动迭代器通过改变给定迭代器的解引用运算符的行为来适配此迭代器
我们可以通过标准库的make_move_iterator函数,将也给普通的迭代器转化为一个移动迭代器。
重写上述reallocate函数如下:

noexcept

移动构造函数通常应该是noexcept

由于移动构造”窃取“资源,它通常不分配任何资源。
因此,移动操作通常不会抛出任何异常。
当编写一个不抛出任何异常的移动操作时,我们应该将此事通知标准库。
除非标准库知道我们的移动构造不会抛出异常,否则它会认为移动我们的类对象时可能会抛出异常,这样,为了处理这样一种可能发送的情况,它就会做一些额外的工作。
noexcept是C++11标准引入的,我们可以用它来承诺一个函数不会抛出异常。
使用方法就是在一个函数的参数列表后面指定noexcept
可见上一条Aet类中移动构造出noexcept的用法。

使用情况

  • 我们确认函数不会抛出异常
  • 我们根本不知道该如何处理异常

实参

运算符

引用限定成员函数

右值和左值引用成员函数

在旧标准中,我们是没办法阻止这种用法的。为了向后兼容,C++11标准仍允许向右值赋值,(但是我们可能希望自己的类中阻止这种行为)同时,我们可以强制左侧运算符是一个左值。
这个时候,我们需要在参数列表后放置一个引用限定符&

引用限定符,可以是&,也可以是&&。它们分别指向一个左值或右值。
引用限定符只能用于非static成员函数,且必须同时出现在函数的声明和定义中。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2024-10-08
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享