自学内容网 自学内容网

【C++】动态内存与智能指针——shared_ptr 和 new 结合使用

12.1.3 shared_ptr 和 new 结合使用

如上文所述,如果我们不初始化一个智能指针,那么它将会被初始化为一个空指针(需要注意的是,智能指针与普通指针在此处有着非常明显的区别。如果只声明某个类型的普通指针,而不对它进行初始化,那么这将会是危险的,因为不经初始化的普通指针是空悬指针,它所指向的地址是未知的。而不经初始化的智能指针是一个空指针,它是安全的)。还可以用 new 返回的指针来初始化智能指针:

shared_ptr<double> p1;// shared_ptr 可以指向一个 double
shared_ptr<int> p2(new int(42));// p2 指向一个值为 42 的 int

接受指针参数的智能指针构造函数是 explicit 的。因此不能将一个内置隐式指针转换为一个智能指针,必须使用直接初始化形式来初始化一个普通指针:

shared_ptr<int> p1 = new int(1024);// 错误❌: 必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));// 正确👌: 使用了直接初始化形式

p1 的初始化隐式地要求编译器用一个 new 返回的 int* 来创建一个 shared_ptr。由于我们不能进行内置指针到智能指针的隐式转换,因此这条初始化语句是错误的。出于相同的原因,一个返回 shared_ptr 的函数不能在其返回语句中隐式转换一个普通指针:

shared_ptr<int> clone(int p) {
return new int(p);// 错误❌: 隐式转换为 shared_ptr<int>
}

(这一部分很好理解,C++ 智能指针的规则就是不能将普通指针隐式地转换为智能指针,由于 new 的返回值是某个类型的普通指针,普通指针不能隐式转为智能指针,因此使用 new … 对智能指针进行赋值是非法的)

我们必须将 shared_ptr 显式绑定到一个想要返回的指针上:

shared_ptr<int> clone(int p) {
return shared_ptr<int>(new int(p)); // 正确👌: 显式地使用 int* 来创建 shared_ptr<int>
}

默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用 delete 释放它所关联的对象。

可以将智能指针绑定到一个指向其它类型的资源的指针上,但是为了这样做,我们必须提供自己的操作来替代 delete。
在这里插入图片描述在这里插入图片描述

不要混合使用普通指针和智能指针 … …

shared_ptr 可以协调对象的析构,但这仅限于其自身的拷贝(也是 shared_ptr)之间。这也是 C++ Primer 推荐使用 make_shared 而不是 new 的原因。

这样,我们就可以在分配对象的同时将 shared_ptr 与之绑定,从而避免了无意中将同一块内存绑定到多个独立创建的 shared_ptr 上。

考虑下面对 shared_ptr 进行操作的函数:

// 在函数调用时 shared_ptr 被创建并初始化
void process(shared_ptr<int> ptr) {
// 使用 ptr
}// ptr 离开作用域, 被销毁

process 的参数是传值方式调用的,因此实参会被拷贝到 ptr 中。拷贝一个 shared_ptr 会递增其引用计数,因此,在 process 运行过程中,引用计数值至少为 2。当 process 结束时,ptr 的引用计数递减,但不会变为 0。因此,当局部变量 ptr 被销毁时,ptr 指向的内存不会被释放。

使用此函数的正确方法是传递给它一个 shared_ptr:

shared_ptr<int> p(new int(42));// 引用计数为 1
process(p);// 拷贝 p 会递增它的引用计数; 在 process 中引用计数值为 2
int i = *p;// 正确: 引用计数值为 1

虽然不能传递给 process 一个内置指针,但可以传递给它一个(临时的)shared_ptr,这个 shared_ptr 是用一个内置指针显式构造的。但这样做可能会导致错误:

int *x(new int(1024)); // 危险: x 是一个普通指针, 不是一个智能指针;
process(x);// 错误❌: 不能将 int* 转换为一个 shared_ptr<int>;
process(shared_ptr<int>(x));// 合法的, 但内存会被释放;
int j = *x;// 未定义的, x 是一个空悬指针;

在上面的调用中,第一行建立一个指向动态内存的 x 指针,它是一个 int 类型的指针,指向的值为 1024,并且它是一个普通指针。第二行是非法的,因为 shared_ptr 和 普通指针之间没有隐式转换。第三行是正确的,因为第三行的实参中显式地将普通指针转为智能指针,但是智能指针所指向的地址会在函数调用结束时被释放。因此,第四行是危险的,因为 x 所指向的内存已经被释放(被释放的过程在 C++ Primer 当中的解释不是很详尽,我的理解是这样的:当第三行对 process 进行调用时,x 被显式地转换为智能指针,指向 x 所指地址的引用计数被置为 1。当函数调用结束时,x 所指地址的引用计数被置为 0,该部分的内存随之被释放。此时,由于函数调用外的 x 是一个普通指针,它所指向的地址仍然是最初的地址,而最初地址的内存在 process 函数调用后被释放,所以此时 x 指向的地址被释放,x 变为一个危险的空悬指针)。

当将一个 shared_ptr 绑定到一个普通指针时,我们就将内存的管理责任交给了这个 shared_ptr。一旦这样做了,我们就不能再使用内置指针来访问 shared_ptr 所指向的内存了。

使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时被销毁。

… … 也不要使用 get 初始化另一个智能指针或为智能指针赋值

智能指针类型定义了一个名为 get 的函数,它返回一个内置指针,指向智能指针关联的对象。此函数是为了这样一种情况而设计的:我们需要向一个不能使用智能指针的代码传递一个内置指针。使用 get 返回的指针的代码不能 delete 此指针。

虽然编译器不会给出错误信息,但将另一个智能指针也绑定到 get 返回的指针上是错误的:

shared_ptr<int> p(new int(42));// 引用计数为 1
int *q = p.get();// 正确👌: 但使用 q 时要注意, 不要让它管理的指针被释放
{// 新程序块
shared_ptr<int>(q);// 未定义: 两个独立的 shared_ptr 指向相同的内存
}// 程序块结束, q 被销毁, 它指向的内存被释放
int foo = *p;// 未定义: p 指向的内存已经被释放;

在本例中,p 和 q 指向相同的内存。由于它们是相互独立创建的,因此各自的引用计数都是 1。当 q 所在的程序块结束时,q 被销毁,这会导致 q 指向的内存被释放。从而 p 变成一个空悬指针。

get 用来将指针的访问权限传递给代码,你只有在确定代码不会 delete 指针的情况下,才能使用 get。

其它 shared_ptr 操作

shared_ptr 还定义了一些其它操作。可以用 reset 来将一个新的指针赋予一个 shared_ptr:

p = new int(1024);// 错误❌: 不能将一个指针赋予 shared_ptr
p.reset(new int(1024));// 正确👌: p 指向一个新对象

与赋值类似,reset 会更新引用计数,如果需要的话,会释放 p 指向的对象。reset 成员常与 unique 一起使用,来控制多个 shared_ptr 共享的对象。


原文地址:https://blog.csdn.net/Coffeemaker88/article/details/144161510

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!