【问题标题】:std::unique_ptr<T[]> API prohibits derived-to-base pointer conversionsstd::unique_ptr<T[]> API 禁止派生到基指针的转换
【发布时间】:2019-02-02 15:41:51
【问题描述】:

Modern Effective C++中,“Iterm 19:使用std::shared_ptr 进行共享所有权资源管理。”,第 133-134 页,它说:

std::shared_ptr 支持派生到基指针的转换,这使得 感知单个对象,但在类型系统中打开漏洞时 应用于数组。 (因此,std::unique_ptr API 禁止此类转换。)

“类型系统中的漏洞”是什么意思?

为什么std::unique_ptr&lt;T[]&gt; API 会禁止派生到基址的指针转换?

它怎么能禁止转换?

【问题讨论】:

  • 实际上,我将放弃我的答案,因为 (a) 受骗,并且 (b) 因为我不 认为 它回答了 关于类型系统漏洞的具体问题
  • @WhozCraig 为什么你认为你的回答没有解决问题? potential dupe I found 的答案没有提到潜在的问题,即内存布局,你很好地解释了。您只是错过了禁止转换的机制,其中unique_ptr 的clang 似乎明确使用了SFINAE godbolt.org/z/LGfULn

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


【解决方案1】:

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

假设你有两个简单的类:

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

数组中的前两个对象会将第二个和第四个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 指针数组,您可能会想,“这很好,对吧”

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 没有。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-06
    • 2021-02-18
    • 1970-01-01
    • 2011-02-01
    • 2019-07-10
    • 2013-04-14
    • 2013-09-23
    相关资源
    最近更新 更多