我知道明确delete 释放函数的一般原因有两个:拒绝不需要的隐式转换,以及为用户提供更好的错误体验。
拒绝不需要的隐式转换
const 的一个有用特性是临时对象可以绑定到对const 的引用。所以这行得通:
void foo(const int& );
foo(42); // ok
临时的42 绑定到函数的引用参数,并且它的生命周期与该引用参数绑定。
现在,考虑std::cref()。我们的目标是通过这个reference_wrapper 到某个地方,所以我们需要底层引用来保持活力。如果我们只是有这个重载:
template <class T>
reference_wrapper<const T> cref(const T&) noexcept;
然后我可以写std::cref(42)。这会很好,我会得到一个std::reference_wrapper<const int> - 除非它是一个悬空的参考。该代码不可能工作。
为了修复这个明显的错误,我们也有这个重载:
template <class T> void cref(const T&&) = delete;
也就是说,我们正在显式删除(或定义为已删除)采用任何右值的重载。现在,在进行重载解析时,当我传入一个右值时,第二次重载是首选,并且该重载格式不正确,编译器会通知我们我们的错误(愚蠢的我,我不能这样做cref(42)!)我不得不花几个小时在 gdb 上试图弄清楚为什么我没有对象。
标准库中的其他示例有:
为用户提供更好的诊断
另一类示例可能是为受约束的函数提供更好的诊断。假设我有一个只对整数类型有意义的函数:
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
void foo(T);
我尝试用非整数类型调用它:
foo(4.2); // error: no matching function
根据需要,这会失败。但是你得到的错误并不是很有意义。特别是如果有其他重载。有了概念画板,这会更好——希望,但不一定。
但是如果我添加相反的显式删除重载:
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
void foo(T);
template <typename T, std::enable_if_t<!std::is_integral_v<T>, int> = 0>
void foo(T) = delete;
foo(4.2); // error: use of deleted function
这更加明确和直接。特别是如果foo 的作者提供了说明为什么这很重要的评论。基本上,这仍然是 SFINAE 友好的,同时还提供了 static_assert 直接指示失败的好处(而如果我们只是 static_asserted,我们会得到更清晰的信息,但我们会失去 SFINAE 友好性)。
确实,N4186 的动机是让该注释成为代码本身的一部分(尽管该提议被拒绝了)。那篇论文中的例子是:
template <typename T>
enable_if_t<has_compatible_vector_size<simd_float, T>::value, simd_float>
operator+(simd_float, T);
template <typename T>
enable_if_t<!has_compatible_vector_size<simd_float, T>::value, simd_float>
operator+(simd_float, T) = delete;
标准库中的一个例子是std::make_unique() 代表make_unique<U[N]>。