第 12 章 动态内存(2)

12.1.3 shared_ptr 和 new 结合使用
shared_ptr<double> p1;  // shared_ptr 指向一个 double
shared_ptr<int> p2(new int(42));  // p2 指向一个值为 42 的 int
  • 定义和改变 shared_ptr 的其他方法
shared_ptr<T> p(q)  // p 管理内置指针 q 所指向的对象; q 必须指向 new 分配的内存,且能够转换为 T*类型
shared_ptr<T> p(u)  // p 从 unique_ptr u 那里接管了对象的所有权,将 u 置为空
p.reset()  // 若 p 是唯一指向其对象的 shared_ptr , reset 会释放此对象
p.reset(q) // 若传递了可选的参数内置指针 q ,会令 p 指向 q,否则会将 p 置为空
  • 不要使用 get 初始化另一个智能指针或为智能指针赋值
    智能指针类型定义了一个名为 get 的函数,它返回一个内置指针,指向智能指针管理的对象。此函数是为了这样一种情况而设计的: 我们需要向不能使用智能指针的代码传递一个内置指针。使用 get 返回的指针的代码不能 delete 此指针
    get 用来将指针的访问权限传递给代码,你只有在确认代码不会 delete 指针的情况下,才能使用 get 。特别是,永远不要用 get 初始化另一个智能指针或者为另一个智能指针赋值
12.1.4 智能指针和异常
  • 如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放
  • 如果使用内置指针管理内存,且在 new 之后在对应的 delete 之前发生了异常,则内存不会被释放
  • 智能指针的陷阱
    1、不使用相同的内置指针值初始化 (或 reset ) 多个智能指针
    2、不 delete get() 返回的指针
    3、不使用 get() 初始化或 reset 另一个智能指针
    4、如果你使用 get() 返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
    5、如果你使用智能指针管理的资源不是 new 分配的内存,记住传递给它一个删除器
12.1.5 unique_ptr
  • 一个 unique_ptr “拥有”它指向的对象。与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向给定的对象。当 unique_ptr 被销毁时,它所指向的对象也被销毁
  • 与 shared_ptr 不同,没有类似 make_shared 的标准库函数返回一个 unique_ptr 。当我们定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上,类似 shared_ptr ,初始化 unique_ptr 必须采用直接初始化形式
unique_ptr<double> p1; // 可以指向一个 double 的 unique_ptr
unique_ptr<int> p2(new int(42));  // p2 指向一个值为 42 的 int
  • 由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1);  // 错误:unique_ptr 不支持拷贝
unique_ptr<string> p3;
p3 = p2;   // 错误:unique_ptr 不支持赋值
  • unique_ptr 操作
unique_ptr<T> u1  // 空 unique_ptr ,可以指向类型为 T 的对象。u1 会使用 delete
unique_ptr<T, D> u2  // u2 会使用一个类型为 D 的可调用对象来释放它的指针
u = nullptr    // 释放 u 指向的对象,将 u 置为空 
u.release()    // u 放弃对指针的控制权,返回指针,并将 u 置为空 
u.reset()      // 释放 u 指向的对象 
u.reset(q)     // 如果提供了内置指针 q ,令 u 指向这个对象;否则将 u 置为空 
u.reset(nullptr)
  • 虽然我们不能拷贝或赋值 unique_ptr,但可以通过调用 release 或 reset 将指针的所有权从一个 unique_ptr 转移到另一个 unique_ptr
// 将所有权从 p1 (指向 string Stegosaurus ) 转移给 p2
unique_ptr<string> p2(p1.release());  // release 将 p1 置为空
unique_ptr<string> p3(new string("Trex"));
// 将所有权从 p3 转移给 p2
p2.reset(p3.release());  // reset 释放了 p2 原来指向内存
  • release 成员返回 unique_ptr 当前保存的指针并将其置为空。因此,p2 被初始化为 p1 原来保存的指针,而 p1 被置为空

  • 不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的 unique_ptr<string>。最常见的例子是从函数返回一个 unique_ptr<string>

unique_ptr<int> clone(int p){
    // 正确:从 int* 创建一个 unique_ptr<int>
    return unique_ptr<int>(new int(p));
}

// 还可以返回一个局部对象的拷贝
unique_ptr<int> clone(int p){
    unique_ptr<int> ret(new int(p))
    // ...
    return ret;
}

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

12.1.6 weak_ptr
  • weak_ptr 是一种不控制所指向对象生存期的智能指针,它指向由一个 shared_ptr 管理的对象
  • 将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数
  • 一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。即使有 weak_ptr 指向对象,对象也还是会被释放
  • weak_ptr 的名字抓住了这种智能指针 "弱" 共享对象的特点
  • weak_ptr 操作
weak_ptr<T> w      // 空 weak_ptr 可以指向类型为 T 的对象 
weak_ptr<T> w(sp)  // 与 shared_ptr sp 指向相同对象的 weak_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 ;否则返回一个指向 w 的对象的 shared_ptr
  • 当我们创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它
    本例子中 wp 和 p 指向相同的对象。由于是弱共享,创建 wp 不会改变 p 的引用计数;wp 指向的对象可能会被释放掉
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);  // wp 弱共享 p ; p 的引用计数未改变
  • 由于对象可能不存在,我们不能使用 weak_ptr 直接访问对象,而必须调用 lock。此函数检查 weak_ptr 指向的对象是否存在。如果存在,lock 返回一个指向共享对象的 shared_ptr 。与任何其他 shared_ptr 类似,只要此 shared_ptr 存在,它指向的底层对象也就会一直存在。例如
在这段代码中,只有当 lock 调用返回 true 时我们才会进入 if 语句体
在 if 中,使用 np 访问共享对象是安全的

if(shared_ptr<int> np = wp.lock()){ // 如果 np 不为空则条件成立
    // 在 if 中,np 和 p 共享对象
}
#include <iostream>
using namespace std;
#include<memory>

class AA{
    public:
    AA(int oo): kk(oo) {    
        cout << kk << " 构造函数" << endl;  
    }
    ~AA(){
        cout << kk << " 析构函数" << endl;  
    }
    private:
    int kk;
};

int main(){
   shared_ptr<AA> a(new AA(99));
   shared_ptr<AA> b = make_shared<AA>(88);
   return 0;
}

99 构造函数
88 构造函数
88 析构函数
99 析构函数

12.2 动态数组

12.2.1 new 和数组

为了让 new 分配一个对象数组,我们要在类型名之后跟一对方括号,在其中指明要分配的对象的数目。返回值是指向第一个对象的指针

int *pia     = new int[10];       // 10 个未初始化的 int
int *pia2    = new int[10]();     // 10 个值初始化为 0 的 int
string *psa  = new string[10];    // 10 个空 string
string *psa2 = new string[10]();  // 10 个空string

在新标准中,我们还可以提供一个元素初始化器的花括号列表
// 10 个 int 分别用列表中对应的初始化器初始化
int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// 10 个 string,前 4 个用给定的初始化器初始化,剩余的进行值初始化
string *psa3 = new string[10]{"a", "an", "the", string(3, 'x')};
  • 释放动态数组
delete p;      // p 必须指向一个动态分配的对象或为空
delete [] pa;  // pa 必须指向一个动态分配的对象或为空

第二条语句销毁 pa 指向的数组中的元素,并释放对应的内存。
数组中的元素按逆序销毁,即,最后一个元素首先被销毁,然后是倒数第二个,以此类推

如果我们在 delete 一个数组指针时忘记了方括号,或者在 delete 一个单一对象的的指针时使用了方括号,
编译器很可能不会给出警告。我们的程序可能在执行过程中在没有任何警告的情况下行为异常
  • 智能指针和动态数组
标准库提供了一个可以管理 new 分配的数组的 unique_ptr 版本。
为了用一个 unique_ptr 管理动态数组,我们必须在对象类型后面跟一对空方括号

// up 指向一个包含 10 个未初始化 int 的数组
unique_ptr<int[]> up(new int[10]);
up.release(); // 自动用 delete [] 销毁其指针

可以用下标来访问数组中的元素
for(int i=0; i != 10; i++){
    up[i] = i;   // 为每个元素赋予一个新值
}
// 指向数组的 unique_ptr 操作
指向数组的 unique_ptr 不支持成员访问运算符 (点和箭头运算符)
unique_ptr<T[]> u     // u 可以指向一个动态分配的数组,数组元素类型为 T 
unique_ptr<T[]> u(p)  // u 指向内置指针 p 所指向的动态分配的数组。p 必须能转换为类型T*
u[i]    // 返回 u 拥有的数组中位置 i 处的对象,u 必须指向一个数组
与 unique_ptr 不同,shared_ptr 不直接支持管理动态数组
如果希望使用 shared_ptr 管理一个动态数组,必须提供自己定义的删除器

// 为了使用 shared_ptr ,必须提供自己定义的删除器
shared_ptr<int> sp(new int[10], [] (int *p) {delete [] p;});
sp.reset();  // 使用我们提供的 lambda 释放数组,它使用 delete []

shared_ptr 未定义下标运算符,而且智能指针类型不支持指针算术运算
因此,为了访问数组中的元素,必须用 get 获取一个内置指针,然后用它来访问数组元素
for(int i=0; i != 10; i++){
    *(sp.get() + i) = i; // 使用 get 获取内置指针
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349