首页 > 解决方案 > 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 会禁止派生到基指针的转换?

它怎么能禁止转换?

标签: c++c++11shared-ptrunique-ptr

解决方案


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

假设你有两个简单的类:

class A
{
    char i;
};

class B : public A
{
    char j;
};

为简单起见,让我们忽略填充等内容,并假设类型对象A为 1 个字节,类型对象为B2 个字节。

现在,当您有一个 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解释为其成员。并且一半的数组丢失了。jBi

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

但是等等,还有更多。

想象一下,你有 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 没有。


推荐阅读