std :: unique_ptr <T []> API禁止派生到基指针转换

cha*_*ink 14 c++ shared-ptr unique-ptr c++11

Modern Effective C++中,"Iterm 19:std::shared_ptr用于共享所有权资源管理.",第133-134页,它说:

std :: shared_ptr支持对单个对象有意义的派生到基指针转换,但是当应用于数组时,它会在类型系统中打开.(因此,std :: unique_ptr API禁止此类转换.)

"类型系统中的开孔"是什么意思?

为什么std::unique_ptr<T[]>API会禁止派生到基指针转换?

它怎么能禁止转换呢?

Max*_*mer 18

类型系统中的一个漏洞就是当一个类型转换为另一个不兼容的类型时编译器没有捕获.

想象一下,你有两个简单的类:

class A
{
    char i;
};

class B : public A
{
    char j;
};
Run Code Online (Sandbox Code Playgroud)

为简单起见,请忽略填充等内容,并假设类型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 |
=================================
Run Code Online (Sandbox Code Playgroud)

现在假设你有指向这些数组的指针然后将它们转换为另一个,这显然会导致问题:

a cast to B[4]:

=================================
|   0   |   1   |   2   |   3   |
|-------|-------|-------|-------|
| i | j | i | j | x | x | x | x |
=================================
Run Code Online (Sandbox Code Playgroud)

数组中的前两个对象将i第二个和第四个成员解释A为其j成员.第二和第三个成员访问未分配的内存.

b cast to A[4]:

=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i | x | x | x | x |
=================
Run Code Online (Sandbox Code Playgroud)

这里它的其他方式,所有4个对象,现在交替地解释ij2种的B情况下,作为其i成员.并且一半的阵列丢失了.

现在想象一下删除这样一个铸造阵列.将调用哪些析构函数?什么记忆将被释放?你现在处于深深的地狱之中.

但等等,还有更多.

想象一下,你有3个这样的课程:

class A
{
    char i;
};

class B1 : public A
{
    float j;
};

class B2 : public A
{
    int k;
};
Run Code Online (Sandbox Code Playgroud)

现在你创建一个B1指针数组:

B1* b1[4];
Run Code Online (Sandbox Code Playgroud)

如果你把那个数组转换成一个A指针数组你可以想,"这很好,对吧?"

A** a = <evil_cast_shenanigans>(b1);
Run Code Online (Sandbox Code Playgroud)

我的意思是,您可以安全地访问每个成员作为指针A:

char foo = a[0]->i; // This is valid
Run Code Online (Sandbox Code Playgroud)

但是你也可以这样做:

a[0] = new B2{};   // Uh, oh.
Run Code Online (Sandbox Code Playgroud)

这是一个有效的赋值,没有编译器会抱怨,但是你不能忘记我们实际上正在处理一个创建为B1对象指针数组的数组.它的第一个成员现在指向一个B2对象,你现在可以访问它,B1而不需要编译器说出一个东西.

float bar = b1[0]->j;   // Ouch.
Run Code Online (Sandbox Code Playgroud)

所以你又陷入了沉闷的境地,编译器将无法向你发出警告,除非首先不允许这种向上转换.

为什么std :: unique_ptr API禁止派生到基指针转换?

我希望上面的解释能说明原因.

它怎么能禁止转换?

它根本不提供任何API来进行转换.shared_ptr API具有转换函数static_pointer_cast,例如unique_ptr API没有.