c++ - std::unique_ptrAPI 禁止派生到基指针的转换
问题描述
在Modern Effective C++中,“Iterm 19:std::shared_ptr
用于共享所有权资源管理。”,第 133-134 页,它说:
std::shared_ptr 支持对单个对象有意义的派生到基指针转换,但在应用于数组时会在类型系统中打开漏洞。(因此,std::unique_ptr API 禁止此类转换。)
“类型系统中的漏洞”是什么意思?
为什么std::unique_ptr<T[]>
API 会禁止派生到基指针的转换?
它怎么能禁止转换?
解决方案
类型系统中的漏洞是每当编译器在将类型转换为另一种不兼容类型时没有捕捉到。
假设你有两个简单的类:
class A
{
char i;
};
class B : public A
{
char j;
};
为简单起见,让我们忽略填充等内容,并假设类型对象A
为 1 个字节,类型对象为B
2 个字节。
现在,当您有一个 typeA
数组或 type 数组时B
,它们将如下所示:
A a[4]:
=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i |
=================
B b[4]:
=================================
| 0 | 1 | 2 | 3 |
|-------|-------|-------|-------|
| i | j | i | j | i | j | i | j |
=================================
现在假设您有指向这些数组的指针,然后将一个转换为另一个,这显然会导致问题:
a cast to B[4]:
=================================
| 0 | 1 | 2 | 3 |
|-------|-------|-------|-------|
| i | j | i | j | x | x | x | x |
=================================
数组中的前两个对象将把i
第 2 个和第 4 个的成员解释A
为它们的j
成员。第二个和第三个成员访问未分配的内存。
b cast to A[4]:
=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i | x | x | x | x |
=================
反过来,所有 4 个对象现在交替地将2 个实例的 thei
和 the解释为其成员。并且一半的数组丢失了。j
B
i
现在想象一下删除这样一个强制转换的数组。将调用哪些析构函数?什么内存会被释放?此时你正处于地狱的深渊。
但是等等,还有更多。
想象一下,你有 3 个这样的类:
class A
{
char i;
};
class B1 : public A
{
float j;
};
class B2 : public A
{
int k;
};
现在您创建一个指针数组B1
:
B1* b1[4];
如果您将该数组转换为一个A
指针数组,您可能会想,“这很好,对吧”?
A** a = <evil_cast_shenanigans>(b1);
我的意思是,您可以安全地访问每个成员作为指向A
:
char foo = a[0]->i; // This is valid
但你也可以这样做:
a[0] = new B2{}; // Uh, oh.
这是一个有效的赋值,没有编译器会抱怨,但你不能忘记我们实际上是在处理一个数组,它是作为指向B1
对象的指针数组创建的。它的第一个成员现在指向一个B2
对象,您现在可以访问它,就像B1
编译器不说话一样。
float bar = b1[0]->j; // Ouch.
因此,您又一次陷入了地狱,编译器将无法警告您,除非首先不允许向上转换。
为什么 std::unique_ptr API 会禁止派生到基址的指针转换?
我希望上面的解释能给出很好的理由。
它怎么能禁止转换?
它根本不提供任何 API 来进行转换。shared_ptr API 具有类似的转换功能static_pointer_cast
,而 unique_ptr API 没有。
推荐阅读
- java - 如何从 JsonNode 对象中检索以特定字母开头的 JSON 元素列表?
- algorithm - 如何确定标签偏移量以使标签始终位于多边形的外部?
- reactjs - 将 React 应用程序导入现有的 Webpack 项目
- html - 它没有得到正确的信息
- c++ - 为什么编译器会抱怨移动这个 lambda?
- sql - 使用源上的日期范围进行 SQL 合并
- flutter - RangeError(索引):无效值:不在 0..6 范围内,包括:-2
- python - Anaconda Prompt 和 Anaconda Powershell Prompt 有什么区别?
- android - 一个应用可以在未经许可的情况下启动其他应用吗?
- node.js - 'cluster' 和 'worker_threads' 如何在 Node.js 中工作?