我正在查看std::unique_ptr
的cpp首选项文档,并注意到C++17似乎做了一些有趣的更改。特别是,std::unique_ptr<T[]>
的专用化现在接受模板参数,而以前只接受std::unique_ptr::pointer
参数。例如,下面是std::unique_ptr<T[]>
的reset
成员函数之一的声明:
template <typename U> void reset(U p);
该网站指出:
行为与主模板的
reset
成员相同,只是它只会参与重载解析,如果U
与pointer
的类型相同,或pointer
与element_type*
的类型相同,U
是指针类型V*
以便V(*)[]
可转换为element_type(*)[]
。
我假设这样做是为了安全 - 您不希望对指向派生类型数组的指针执行delete[]
,该数组分配给指向其基类型的指针(在 C++17 之前,这被标记为已删除)。正如预期的那样,这段代码编译得很好:
#include <type_traits>
struct foo {};
struct bar : public foo {};
static_assert(!std::is_convertible_v<bar(*)[], foo(*)[]>);
然而,有趣的是,以下内容无法编译,static_assert
都失败了:
#include <type_traits>
struct foo {};
struct bar : public foo {};
static_assert(std::is_convertible_v<bar*(*)[], foo*(*)[]>);
static_assert(std::is_convertible_v<std::unique_ptr<bar>(*)[], std::unique_ptr<foo>(*)[]>);
为什么?在什么情况下会使用此重载?
这基本上是一种说法,"如果这样做是安全的,你可以传递一个不太恒定的指针",例如,int* p = /*...*/; unique_ptr<const int []> up; up.reset(p);
对于不同类型的U
和V
,U(*)[]
(隐式)可转换为V(*)[]
的唯一情况是通过限定转换,即,当您在类型中的正确位置添加const
/volatile
时。确切的规则很复杂(因为它们处理任意嵌套的指针/指向成员/数组的指针;如果您想知道,请单击链接),但它们基本上只允许在安全的情况下进行转换;然后,unique_ptr
的规范利用了这一事实,这样它就不必重新定义"安全",代价是使意图更加神秘。
struct foo {};
struct bar : public foo {};
在这种微不足道的情况下,foo*
的值表示很可能与bar*
的值表示完全相同,因此可以在它们的数组之间进行转换。然而,一旦bar
变得更加复杂,它就不再成立。考虑一下:
struct foo {};
struct qux {};
struct bar : qux, foo {};
现在,bar*
可以隐式转换为foo*
,但该转换不会保持其确切值:它需要从qux
到foo
的偏移量。
同样,一旦虚拟、多个访问控制级别等进入图片,基类子对象的地址就会变得与派生最多的对象的地址不同。此类指针甚至可以具有不同的大小(1)。
这就是为什么只有有限的一组情况可以将derived*
用作base*
而无需更改值的转换。我的猜测是,为有限的情况提供允许会使标准复杂化,几乎没有什么好处,因此根本不允许此类指针数组之间的转换。
(1)这是一个有点晦涩的情况,但如果C++编译器受到设计不佳的 C ABI 的约束,则可能会发生这种情况。这个答案讨论了这种情况。