为什么unique_ptr的模板参数无法推导?

24

当你在C++17中使用类模板参数推断时,为什么无法推断std::unique_ptr的模板参数?例如,以下代码会导致编译错误:

std::unique_ptr smp(new D);

这表示“类模板的参数列表缺失”。

模板参数(至少是指针类型)不应该是可推导的吗?

参见此链接:

任何指定变量和变量模板初始化的声明


1
不是回答,只是好奇为什么你一开始没有使用make_unique呢..? - Jesper Juhl
C++17增加了类模板推导指南,使用构造函数调用来推断类的模板类型。 - NathanOliver
1
@Someprogrammerdude,那cppreference的措辞难道不会有些误导吗?(我正打算在问题中加上引文:)) - That Guy
对我来说,我想使用结构体聚合初始化器。make_unique似乎不支持它。 @JesperJuhl - Yongwei Wu
3个回答

27

让我们看一下 new intnew int[10]。这两者都返回一个 int*。没有办法确定你应该使用 unique_ptr<int> 还是 unique_ptr<int[]>。这就足以不提供任何推断指南。


3
我理解你的想法,但这个情况是否有相应的规则呢?比如说,如果......那么就必须明确定义模板参数。 - That Guy
7
哦,那么默认使用unique_ptr<int>,如果你想要数组,则必须明确指定?我认为这可能更麻烦,因为你需要记住什么情况下需要特别指定,而不是直接告诉它你需要什么。 - NathanOliver
我需要std::unique_ptr来跟踪我的数组指针。std::make_unique用零初始化数组。std::make_unique_for_overwrite将在C++20中可用。因此,如果我想创建unique_ptr,我必须两次写入类型std::unique_ptr<char[]>(new char[128])而不是std::unique_ptr(new char[128]) - Dmytro Ovdiienko
@DmytroOvdiienko 你是在尝试创建一个缓冲区并填充数据吗?如果是这种情况,有没有使用std::vector的原因呢? - NathanOliver
当使用std::unique_ptr的二元构造函数时,删除器(deleter)的类型至少可以被推断出来,对吗? - user2023370
显示剩余3条评论

17

我不会重复@NathanOliver的优秀答案中的原理,我只会提到它的实现方式,机制,这也是我认为你想知道的。如果unique_ptr的构造函数仅仅是这样的话,你是对的...

explicit unique_ptr( T* ) noexcept;

如果我们能够推断出T,那么编译器生成的推断指南就可以正常工作。这将是一个问题,正如Nathan所示。但构造函数的规定如下...

explicit unique_ptr( pointer p ) noexcept;

...其中别名pointer的指定如下:

pointer:如果该类型存在,则为std::remove_reference<Deleter> :: type :: pointer,否则为T * 。必须满足NullablePointer

这个规范实际上意味着pointer必须是__some_meta_function<T> :: type的别名。冒号左边的所有内容都是不可推导的上下文,这就防止了从pointer中推导出T。即使pointer始终需要是T*,只要将其作为不可推导的上下文,也可以使这种推导指南失败。这样做只是为了防止从该构造函数产生任何推导指南的可行性。


3
正是我在寻找的答案。谢谢。 - That Guy

0

这是C++早期的一个副作用,当时标准制定者决定为指向对象和指向对象数组的指针分别设置两个不同的deletedelete[]运算符。

在现代C++中,我们有模板(它们从一开始就不存在)、std::array(用于固定大小的数组)、初始化列表(用于静态固定大小的数组)和std::vector(用于动态大小的数组),几乎没有人再需要delete[]运算符了。我从未使用过它,如果这个问题的大多数读者也没有使用过它,我也不会感到惊讶。

int* array = new int[5];替换为auto* array = new std::array<int, 5>;会简化事情,并使指针安全地转换为std::unique_ptrstd::shared_ptr。但这会破坏旧代码,到目前为止,C++标准维护者一直非常注重向后兼容性。

然而,没有人阻止你编写一个小型的内联模板包装函数:

template<typename T>
std::unique_ptr<T> unique_obj_ptr(T* object) {
    static_assert(!std::is_pointer<T>::value, "Cannot use pointers to pointers here");
    return std::unique_ptr<T>(object);
}

当然,你也可以创建一个类似的函数shared_obj_ptr()来创建std::shared_ptr,如果你真的需要它们,你还可以添加unique_arr_ptr()shared_arr_ptr()

new std::array<...> 不能用于动态大小的数组,而 new std::vector<...> 则会浪费一次分配。 - n. m.
auto var = new TYPE[N](其中 N 不是常量)的替代方法是 auto var = std::vector<TYPE>(N)。因此,不会浪费任何分配,只需要额外的空间来存储向量数据结构中当前使用和已分配大小的空间。对于非平凡析构函数,这两个值之一,即数组大小,也会在隐藏字段中保留以便由 delete [] 运算符进行正确的销毁。如果担心浪费另外八个字节的当前大小,则请将不可调整大小的向量添加到标准库中。 - Kai Petzke
auto var = std::vector<TYPE>(N) 本质上是一个独特的数组。没有共享或非拥有的对应物。 - n. m.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接