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

std::unique_ptr<T[]> API prohibits derived-to-base pointer conversions

Modern Effective C++, "Iterm 19: Use std::shared_ptr for shared-ownership resource management.", 第 133-134 页,它说:

std::shared_ptr supports derived-to-base pointer conversions that make sense for single objects, but that open holes in the type system when applied to arrays. (For this reason, the std::unique_ptr API prohibits such conversions.)

"open holes in the type system"是什么意思?

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

它怎么能禁止转换呢?

类型系统中的漏洞 是编译器在将类型强制转换为另一个不兼容的类型时没有捕捉到。

假设你有两个简单的 类:

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 |
=================================

数组中的前两个对象会将第 2 个和第 4 个 Ai 成员解释为它们的 j 成员。第二和第三个成员访问未分配的内存。

b cast to A[4]:

=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i | x | x | x | x |
=================

反过来,所有 4 个对象现在交替解释 2 个 B 实例的 ij 作为它们的 i 成员。并且数组的一半丢失了。

现在想象一下删除这样一个强制转换的数组。将调用哪些析构函数?什么内存将被释放?此时你已经深陷地狱了。

等等,还有更多。

假设你有 3 个 类 这样的:

class A
{
    char i;
};

class B1 : public A
{
    float j;
};

class B2 : public A
{
    int k;
};

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

B1* b1[4];

如果将该数组转换为 A 个指针数组,您可能会想,"well this is fine, right"?

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_ptrAPI有static_pointer_cast的转换功能,unique_ptrAPI没有