在现代有效的C ++ 中,“条款19:使用std::shared_ptr
进行共享所有权资源管理。”,第133-134页,它说:
std :: shared_ptr支持派生到基址的指针转换,该转换使 单个对象的感觉,但是当 适用于数组。 (因此,std :: unique_ptr API 禁止此类转换。)
“类型系统中的开孔”是什么意思?
为什么std::unique_ptr<T[]>
API禁止派生到基址的指针转换?
又如何禁止转换?
答案 0 :(得分:18)
类型系统中的一个空洞是当将类型强制转换为另一种不兼容类型时编译器无法捕获的情况。
想象一下,您有两个简单的类:
class A
{
char i;
};
class B : public A
{
char j;
};
为了简单起见,我们忽略诸如填充等之类的内容,并假设类型A
的对象为1个字节,类型B
类型的对象为2个字节。
现在,当您拥有A
类型的数组或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
的{{1}}成员解释为它们的A
成员。第二和第三成员访问未分配的内存。
j
与此相反,现在所有4个对象将2个b cast to A[4]:
=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i | x | x | x | x |
=================
实例中的i
和j
交替解释为它们的B
成员。而且阵列的一半丢失了。
现在想象删除这样的转换数组。哪些析构函数将被调用?什么内存将被释放?您现在处于深渊中。
但是,还有更多。
想象一下,您有3个这样的课程:
i
现在您创建了一个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
因此,您再次陷入困境,除非首先禁止向上转换,否则编译器将无法警告您。
为什么std :: unique_ptr API禁止派生到基址的指针转换?
我希望以上解释能为您提供充分的理由。
如何禁止转换?
它根本不提供任何API进行转换。 shared_ptr API具有类似float bar = b1[0]->j; // Ouch.
的转换功能,unique_ptr API没有。