【发布时间】:2020-12-08 22:21:27
【问题描述】:
我在说什么
我指的是 std::unique_ptr<T,Deleter>::unique_ptr 的 3 和 4,它们有这个签名:
unique_ptr( pointer p, /* see below */ d1 ) noexcept;
我的问题
主要是这些:
-
/* see below */的解释究竟是什么意思? - 作为程序员,在选择将什么作为删除器类型模板参数传递给
std::unique_ptr时,我该如何使用它?
而且,更详细:
-
std::unique_ptr的构造函数是模板化的事实是否是必须提供删除器模板参数的原因? - 如果上一个问题的答案是肯定的,那么句子The program is ill-formed if this two constructors are selected by class template argument deduction是什么意思?
-
_Dp和_Del究竟有何不同,这有多重要?
我试图绕过它的失败尝试
在这里我试图解释我的推理。上面预期的一些问题也散布在文本中。
我的理解是,在C++17之前,模板类型推导不适用于类,只适用于函数,所以在创建模板类的实例时,比如std::unique_ptr,都是强制的(即没有@ 987654331@) 模板类的模板参数必须通过<…>提供。
此外,在/usr/include/c++/10.2.0/bits/unique_ptr.h 中,我或多或少看到了这个:
namespace std {
// …
template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr {
public:
// …
using deleter_type = _Dp;
// …
template<typename _Del = deleter_type, typename = _Require<is_copy_constructible<_Del>>>
unique_ptr(pointer __p, const deleter_type& __d) noexcept : _M_t(__p, __d) { }
// …
}
// …
}
构造函数在类型参数_Del上被模板化,默认为类'deleter_type(这是_Dp的别名);据我了解,如果我错了,请纠正我 (*),std::unique_ptr 甚至无法利用 C++17 的类模板类型推导,因此就重载而言,_Dp 的模板参数仍然是强制性的(即,如果要将删除器对象作为第二个参数传递给构造函数)。
既然是这种情况,我们传递给std::unique_ptr 的实际类型参数可以用引用声明符进行修饰,如链接页面中所述。但这就是我迷路的地方,更不用说我确实看到一般_Dp 和_Del 可以有所不同(例如,它们可能因引用声明符而异),这使我的理解更加复杂。
不过,我将复制页面中解释各种可能情况的部分内容:
3-4) 构造一个拥有
p的std::unique_ptr对象,用p初始化存储的指针并初始化一个删除器D如下(取决于D是否为引用类型)
a) 如果
D是非引用类型A,则签名为:unique_ptr(pointer p, const A& d) noexcept; unique_ptr(pointer p, A&& d) noexcept;b) 如果
D是左值引用类型A&,则签名为:unique_ptr(pointer p, A& d) noexcept; unique_ptr(pointer p, A&& d) = delete;c) 如果
D是一个左值引用类型const A&,那么签名是:unique_ptr(pointer p, const A& d) noexcept; unique_ptr(pointer p, const A&& d) = delete;在所有情况下,删除器都是从
std::forward<decltype(d)>(d)初始化的。这些重载仅在std::is_constructible<D, decltype(d)>::value为true时参与重载解析。
我能理解引用的文字的唯一方法如下,有很多疑问。
- 如果我们想将删除器
d作为参数传递给构造函数,我们必须明确地将D作为模板参数传递给...什么?到class和/或它的构造函数?甚至可以将模板参数传递给构造函数吗? - 那个
D可以是三种- 如果我们将其指定为
A,这意味着我们希望能够将(可能是const)左值或右值作为d传递,因此采用const A&和A&&的重载都是已定义。 - 如果我们将其指定为
<s>const</s> A&,这意味着我们希望无法将右值作为d传递,因此重载采用@987654371 @ 被删除,因为它会绑定到右值,而重载A&用于代替const A&,因为后者也会绑定到右值。 - 如果我们将其指定为
const A&,这意味着我们希望能够将both一个左值或一个右值作为d传递,所以重载采用const A&是要选择的,而采用const A&&的另一个是deleted,因为该参数类型无法绑定到左值,并且它对待右值的处理方式与const A&没有区别, 正如答案中所解释的,最重要的是,它绑定到右值,防止其他重载const A&绑定到右值,这将导致悬空引用存储在std::unique_ptr中(原因因为这是here)。
- 如果我们将其指定为
- 但是,当右值作为
d传递时,1. 和 3. 的不同用例是什么? 1.绑定A&&和3.绑定const A&,前者可以盗取资源,后者不能。
最后但同样重要的是,链接页面还添加了 C++17 特有的内容:
如果通过类模板参数推导选择这两个构造函数中的任何一个,则程序是非良构的。
根据我的理解(参见上面的(*)),我根本不清楚:这些构造函数如何进行类型推断? p>
所以底线问题是:这种复杂性在声明std::unique_ptr<T,Deleter>::unique_ptr 的方式对我作为程序员有用吗?
【问题讨论】:
-
我认为需要记住的是,
std::unique_ptr与原始指针相比尝试没有内存开销,它只能用于某些类型的析构函数。这可能会在他们必须如何定义构造函数时引入一些额外的复杂性。
标签: c++ templates c++17 unique-ptr type-deduction