对 const 的右值引用有啥用吗?
Posted
技术标签:
【中文标题】对 const 的右值引用有啥用吗?【英文标题】:Do rvalue references to const have any use?对 const 的右值引用有什么用吗? 【发布时间】:2011-02-08 21:49:03 【问题描述】:我猜不是,但我想确认一下。 const Foo&&
有什么用,其中Foo
是类类型?
【问题讨论】:
在这个视频中,STL 说const&&
非常重要,虽然他没有说明原因:youtube.com/watch?v=JhgWFYfdIho#t=54m20s
【参考方案1】:
它们有时很有用。 C++0x 草案本身在一些地方使用它们,例如:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
上述两个重载确保其他 ref(T&)
和 cref(const T&)
函数不绑定到右值(否则可能)。
更新
我刚刚检查了官方标准N3290,遗憾的是它没有公开,它在 20.8 函数对象 [function.objects]/p2 中有:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
然后我查看了最新的 C++11 后草案,该草案是公开的,N3485,在 20.8 函数对象 [function.objects]/p2 中仍然显示:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
【讨论】:
为什么您的答案中包含相同的代码三次?我试图找到不同的地方太久了。 @typ1232:似乎我在回答它近 2 年后更新了答案,因为 cmets 担心引用的函数不再出现。我从 N3290 和当时最新的草稿 N3485 进行了复制/粘贴,以显示功能仍然出现。在我当时的想法中,使用复制/粘贴是确保比我更多的眼睛可以确认我没有忽略这些签名中的一些细微变化的最佳方式。 验证两个sn-ps等价的过程:将第一个sn-p复制到文本文件中;将不相关的 sn-p 复制到第二个文本文件中;将第二个 sn-p 复制到第三个文本文件中。使用diff
验证第一个和最后一个文件是否相等,第二个不同。
@kevinarpe:“其他重载”(此处未显示,但在标准中)采用左值引用,并且不会被删除。此处显示的重载比采用左值引用的重载更适合右值。所以右值参数绑定到这里显示的重载,然后因为这些重载被删除而导致编译时错误。
const T&&
的使用防止有人愚蠢地使用 ref<const A&>(...)
形式的显式模板参数。这不是一个非常有力的论据,但const T&&
相对于T&&
的成本非常低。【参考方案2】:
获取const rvalue reference(而不是=delete
)的语义是为了说明:
以下用例可能是恕我直言,rvalue reference to const 的一个很好的用例,尽管该语言决定不采用这种方法(请参阅original SO post) .
案例:来自原始指针的智能指针构造函数
通常建议使用make_unique
和make_shared
,但unique_ptr
和shared_ptr
都可以从原始指针构造。两个构造函数都通过值获取指针并复制它。两者都允许(即:不阻止)在构造函数中继续使用传递给它们的原始指针。
以下代码编译并产生double free:
int* ptr = new int(9);
std::unique_ptr<int> p ptr ;
// we forgot that ptr is already being managed
delete ptr;
unique_ptr
和 shared_ptr
如果它们的相关构造函数期望将原始指针 作为 const rvalue 获取,则可以防止上述情况,例如对于unique_ptr
:
unique_ptr(T* const&& p) : ptrp
在这种情况下,上面的 double free 代码将无法编译,但以下代码会:
std::unique_ptr<int> p1 std::move(ptr) ; // more verbose: user moves ownership
std::unique_ptr<int> p2 new int(7) ; // ok, rvalue
请注意,ptr
在移动后仍然可以使用,因此潜在的错误并没有完全消失。但是如果要求用户调用std::move
,这样的错误将属于常见规则:不要使用被移动的资源。
有人可以问:好的,但是为什么T* <b><i>const</i></b>&& p
?
原因很简单,允许创建unique_ptr
从常量指针。请记住,const rvalue reference 比 rvalue reference 更通用,因为它同时接受 const
和 non-const
。所以我们可以允许:
int* const ptr = new int(9);
auto p = std::unique_ptr<int> std::move(ptr) ;
如果我们只期望 rvalue 引用,这将不会发生(编译错误:无法将 const rvalue 绑定到 rvalue)。
无论如何,现在提出这样的事情已经太迟了。但是这个想法确实提出了一个合理使用 对 const 的右值引用。
【讨论】:
我是一群有类似想法的人,但我没有前进的支持。 "自动 p = std::unique_ptr std::move(ptr) ;"编译时出现错误“类模板参数推导失败”。我认为应该是“unique_ptr它们是允许的,甚至函数根据const
进行排名,但由于您无法从const Foo&&
引用的 const 对象中移动,它们没有用处。
【讨论】:
你所说的“排名”究竟是什么意思?我猜这与重载决议有关? 如果给定类型有一个采用 const rvalue-ref 的移动 ctor,为什么不能从 const rvalue-ref 移动? @FredOverflow,过载排名是这样的:const T&, T&, const T&&, T&&
@Fred:不修改源怎么移动?
@Fred:可变数据成员,或者可能为这种假设类型移动不需要修改数据成员。【参考方案4】:
除了std::ref,标准库还在std::as_const中使用了const rvalue引用来达到同样的目的。
template <class T>
void as_const(const T&&) = delete;
在std::optional获取包装后的值时也用作返回值:
constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;
以及std::get:
template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;
这大概是为了在访问包装值时保持值类别以及包装器的常量。
这对是否可以在包装对象上调用 const rvalue ref-qualified 函数产生影响。也就是说,我不知道 const rvalue ref 限定函数的任何用途。
【讨论】:
【参考方案5】:我想不出这会直接有用的情况,但它可能会被间接使用:
template<class T>
void f(T const &x)
cout << "lvalue";
template<class T>
void f(T &&x)
cout << "rvalue";
template<class T>
void g(T &x)
f(T());
template<class T>
void h(T const &x)
g(x);
g中的T是T const,所以f的x是T const&&。
这很可能会导致 f 中的 comile 错误(当它试图移动或使用对象时),但 f 可以采用右值引用,这样它不能在不修改右值的情况下在左值上调用(如上面过于简单的示例)。
【讨论】:
以上是关于对 const 的右值引用有啥用吗?的主要内容,如果未能解决你的问题,请参考以下文章
错误:对类型为'const ItemInstance'的引用无法绑定到类型为'void'的右值