我猜不会有用,但我想确认一下。对于一个类类型 Foo
,是否有使用 const Foo&&
的情况?
我猜不会有用,但我想确认一下。对于一个类类型 Foo
,是否有使用 const Foo&&
的情况?
它们偶尔会有用。C++0x草案本身在一些地方使用它们,例如:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
上述两个重载函数确保其他ref(T&)
和cref(const T&)
函数不会绑定到rvalue(否则可能会发生)。
更新
我刚刚查了官方标准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;
diff
命令来验证第一个和最后一个文件相等,而第二个文件则不同。 - filiposconst T&&
可以防止某些人愚蠢地使用形式为ref<const A&>(...)
的显式模板参数。这不是一个非常有力的论点,但相比于T&&
而言,const T&&
的成本非常小。 - Howard Hinnant=delete
)的语义是为了表明:
以下用例可能是对rvalue引用到const的一个很好的用例,尽管语言决定不采用这种方法(请参见原始SO帖子)。
通常建议使用make_unique
和make_shared
,但unique_ptr
和shared_ptr
都可以从裸指针构造。两个构造函数都按值获取指针并复制它。两者都允许(即以不防止的意义)在构造函数中传递的原始指针的继续使用。
以下代码编译并导致双重释放:
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右值的原始指针,例如对于unique_ptr
:
unique_ptr(T* const&& p) : ptr{p} {}
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*
const&& p
?const pointer
创建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)。
不管怎样,现在提出这样的想法已经太晚了。但是这个想法确实展示了对rvalue引用到const的合理使用。
除了std::ref,标准库在std::as_const中也使用const右值引用来实现相同的目的。
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性质的稳定。
这会影响是否可以调用封装对象上的const rvalue ref限定函数。话虽如此,我不知道const rvalue ref限定函数有任何用途。
它们可以根据const
进行排名,甚至可以使用,但由于您无法从const Foo&&
所引用的常量对象中移动,因此它们没有用处。
const T&、T&、const T&&、T&&
。 - Gene Bushuyevconst
意味着对象本身不会改变,除了可变的静态成员(例如 std::map<T*, T*>
,用于指示知道其实例的类型的当前所有者)可能会改变。事实上,如果您是 const-correct,则很有用,因为那么您仍然可以使用旧对象。 - lorro我想不出这种情况直接有用的场景,但它可能会间接使用:
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是一个常量,所以f的x也是一个T常量,并且。。。
这很可能会导致f在尝试移动或使用对象时出现编译错误,但f可以采用rvalue-ref方式,因此它不能被调用为lvalue,而不修改rvalue(如上面太简单的例子)。
Rvalue引用旨在允许移动数据。因此,在绝大多数情况下,它的使用是无意义的。
你会发现主要的边缘情况是为了防止人们使用rvalue调用函数:
template<class T>
void fun(const T&& a) = delete;
与非 const 版本相比,const 版本将涵盖所有的边界情况。
这就是为什么,考虑这个例子:
struct My_object {
int a;
};
template<class T>
void fun(const T& param) {
std::cout << "const My_object& param == " << param.a << std::endl;
}
template<class T>
void fun( T& param) {
std::cout << "My_object& param == " << param.a << std::endl;
}
int main() {
My_object obj = {42};
fun( obj );
// output: My_object& param == 42
const My_object const_obj = {64};
fun( const_obj );
// output: const My_object& param == 64
fun( My_object{66} );
// const My_object& param == 66
return 0;
}
现在,如果你想要防止有人使用fun( My_object{66} );
,因为在当前情况下,它将被转换为const My_object&,你需要定义:
template<class T>
void fun(T&& a) = delete;
现在,fun( My_object{66} );
将会抛出一个错误,但是如果有些聪明的程序员决定写:
fun<const My_object&>( My_object{1024} );
// const My_object& param == 1024
这将再次起作用并调用该函数的const lvalue重载版本...幸运的是,我们可以通过向删除的重载添加const来结束这种亵渎:
template<class T>
void fun(const T&& a) = delete;
或许在这种情境下(coliru 链接)可以被认为是有用的:
#include <iostream>
// Just a simple class
class A {
public:
explicit A(const int a) : a_(a) {}
int a() const { return a_; }
private:
int a_;
};
// Returning a const value - shouldn't really do this
const A makeA(const int a) {
return A{a};
}
// A wrapper class referencing A
class B {
public:
explicit B(const A& a) : a_(a) {}
explicit B(A&& a) = delete;
// Deleting the const&& prevents this mistake from compiling
//explicit B(const A&& a) = delete;
int a() const { return a_.a(); }
private:
const A& a_;
};
int main()
{
// This is a mistake since makeA returns a temporary that B
// attempts to reference.
auto b = B{makeA(3)};
std::cout << b.a();
}
它可以防止错误被编译。这段代码还有其他一堆问题,编译器警告会捕捉到,但也许 const&&
有所帮助?
这个帖子中几乎每个人(除了@FredNurk和@lorro)都误解了const
的工作原理,这有点令人不安,所以让我来解释一下。
只有const引用禁止修改类的直接内容。我们不仅有静态和可变成员,可以通过const引用进行修改;而且我们还可以修改存储在非静态、非可变指针引用的内存位置中的类的内容——只要我们不修改指针本身。
这正是一个极为常见的Pimpl惯用法。考虑以下情况:
// MyClass.h
class MyClass
{
public:
MyClass();
MyClass(int g_meat);
MyClass(const MyClass &&other); // const rvalue reference!
~MyClass();
int GetMeat() const;
private:
class Pimpl;
Pimpl *impl {};
};
// MyClass.cpp
class MyClass::Pimpl
{
public:
int meat {42};
};
MyClass::MyClass() : impl {new Pimpl} { }
MyClass::MyClass(int g_meat) : MyClass()
{
impl->meat = g_meat;
}
MyClass::MyClass(const MyClass &&other) : MyClass()
{
impl->meat = other.impl->meat;
other.impl->meat = 0;
}
MyClass::~MyClass()
{
delete impl;
}
int MyClass::GetMeat() const
{
return impl->meat;
}
// main.cpp
const MyClass a {100500};
MyClass b (std::move(a)); // moving from const!
std::cout << a.GetMeat() << "\n"; // returns 0, b/c a is moved-from
std::cout << b.GetMeat() << "\n"; // returns 100500
看啊 - 一个完全功能的、常量正确的移动构造函数,接受常量右值引用。
const&&
非常重要,尽管他没有解释为什么:https://www.youtube.com/watch?v=JhgWFYfdIho#t=54m20s - Aaron McDaid