对常量的右值引用有用吗?

我想没有,但我想确认一下。const Foo&&有什么用处吗? 其中 Foo是一个类类型?

20001 次浏览

They are allowed and even functions ranked based on const, but since you can't move from const object referred by const Foo&&, they aren't useful.

我想不出在什么情况下这会直接有用,但它可以间接使用:

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);
}

The T in is T const, so F's x is an T const&&.

这很可能导致 F中出现编译错误(当它试图移动或使用对象时) ,但是 F可以接受 rvalue-ref,这样就不能在不修改 rvalue 的情况下对 lvalue 进行调用(就像上面的例子一样)。

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 object [ 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 object [ Function. Objects ]/p2中它仍然说:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

除了 std::ref,标准库还使用 : as _ const中的 const rvalue 引用来实现同样的目的。

template <class T>
void as_const(const T&&) = delete;

可选中,当获取包装值时,它也被用作返回值:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

以及 得到:

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 限定函数。也就是说,我不知道 const rvalue ref 限定函数的任何用途。

获得 < strong > const rvalue reference (而不是 =delete)的语义是:

  • 我们不支持左值操作!
  • 即使是 we still copy,因为我们不能 让开传递的资源,或者因为没有实际意义的“移动”它。

The following use case 有可能 IMHO a good use case for 对 const 的 rvalue 引用, though the language decided not to take this approach (see 原来的职位).


案例: 来自原始指针的智能指针构造函数

通常建议使用 make_uniquemake_shared,但是 unique_ptrshared_ptr都可以从原始指针构造。两个构造函数都按值获取指针并复制它。两者都允许(例如: 不要阻止)在构造函数中继续使用传递给它们的原始指针。

以下代码使用 双自由编译并得到结果:

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

如果它们的相关构造函数期望获得原始指针 作为常数值,那么 unique_ptrshared_ptr都可以防止上述操作,例如,对于 unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

在这种情况下,上面的 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

Note that ptr could still be used after it was moved, so the potential bug is not totally gone. But if user is required to call std::move such a bug would fall into the common rule of: do not use a resource that was moved.


One can ask: OK, but why T* const&& p?

原因很简单,就是允许创建 unique_ptr < strong > from const 指针 。记住,常量右值引用右值引用,右值引用更通用,因为它同时接受 constnon-const。因此,我们可以允许以下情况:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

this wouldn't go if we would expect just rvalue reference (compilation error: cannot bind 常数右值 to 价值).


不管怎样,现在提出这样的建议已经太晚了。但是这个想法确实提出了 对 const 的 rvalue 引用的合理用法。

也许在这种情况下(科利鲁链接)它可以被认为是有用的:

#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&&可能会有所帮助吧?

Rvalue 引用意味着允许移动数据。 因此,在绝大多数情况下,它的使用是毫无意义的。

您会发现,主要的优势在于防止人们调用具有右值的函数:

template<class T>
void fun(const T&& a) = delete;

与非常量版本相反,常量版本将覆盖所有边界情况。


下面是原因,考虑一下这个例子:

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;