第 12 章 动态内存(2)

12.1.3 shared_ptr 和 new 结合使用
shared_ptr p1;  // shared_ptr 指向一个 double
shared_ptr p2(new int(42));  // p2 指向一个值为 42 的 int
  • 定义和改变 shared_ptr 的其他方法
shared_ptr p(q)  // p 管理内置指针 q 所指向的对象; q 必须指向 new 分配的内存,且能够转换为 T*类型
shared_ptr 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 p1; // 可以指向一个 double 的 unique_ptr
unique_ptr p2(new int(42));  // p2 指向一个值为 42 的 int
  • 由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作
unique_ptr p1(new string("Stegosaurus"));
unique_ptr p2(p1);  // 错误:unique_ptr 不支持拷贝
unique_ptr p3;
p3 = p2;   // 错误:unique_ptr 不支持赋值
  • unique_ptr 操作
unique_ptr u1  // 空 unique_ptr ,可以指向类型为 T 的对象。u1 会使用 delete
unique_ptr 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 p2(p1.release());  // release 将 p1 置为空
unique_ptr p3(new string("Trex"));
// 将所有权从 p3 转移给 p2
p2.reset(p3.release());  // reset 释放了 p2 原来指向内存
  • release 成员返回 unique_ptr 当前保存的指针并将其置为空。因此,p2 被初始化为 p1 原来保存的指针,而 p1 被置为空

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

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

// 还可以返回一个局部对象的拷贝
unique_ptr clone(int p){
    unique_ptr 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 w      // 空 weak_ptr 可以指向类型为 T 的对象 
weak_ptr 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(42);
weak_ptr 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 np = wp.lock()){ // 如果 np 不为空则条件成立
    // 在 if 中,np 和 p 共享对象
}
#include 
using namespace std;
#include

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

int main(){
   shared_ptr a(new AA(99));
   shared_ptr b = make_shared(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 up(new int[10]);
up.release(); // 自动用 delete [] 销毁其指针

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

// 为了使用 shared_ptr ,必须提供自己定义的删除器
shared_ptr 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 获取内置指针
}

你可能感兴趣的:(第 12 章 动态内存(2))