什么是ref-限定符`const &&`的用处?

43

我在之前的问题后继续研究了一下引用限定符。

给定下面的代码示例:

#include <iostream>
#include <string>
#include <utility>

struct A {
  std::string abc = "abc";
  std::string& get() & {
    std::cout << "get() &" << std::endl;
    return abc;
  }
  std::string get() && {
    std::cout << "get() &&" << std::endl;
    return std::move(abc);
  }
  std::string const& get() const & {
    std::cout << "get() const &" << std::endl;
    return abc;
  }
  std::string get() const && {
    std::cout << "get() const &&" << std::endl;
    return abc;
  }
};

int main()
{
  A a1;
  a1.get();
  const A a2{};
  a2.get();
  A().get();
  const A a3{};
  std::move(a3).get();
}

输出结果如下:

get() &
get() const &
get() &&
get() const &&

这段代码可以在clang和gcc 4.9.1(但不包括4.9.0)上编译运行。 点击此处查看演示。

一般来说,这是一段代码(样例仅用于展示代码的编译和运行情况)。

  • 一个方法被标记为const &&,意味着什么?

该方法无法修改对象的内容(它是const),尝试从const &&方法中返回std::move(abc);实际上并没有移动std::string。 假设你希望能够修改对象,因为它是一个右值,而且不会存在很长时间。 如果删除了const &&限定符,代码std::move(a3).method()将绑定到const &限定符方法,这是有道理的。

  • 如果方法被标记为const &const &&,是否会有任何暗示的语义区别?换句话说,实现会有何不同或为什么需要两者?
  • std::string真正能够从临时对象中“移动”出来吗?
  • 在这种情况下,“std::string get() const &&”的“规范”签名是什么?
4个回答

24

关于const&&的实用性... (总体上)

const&&修饰符在成员方法上的实用性最多只能算是微不足道的。对象无法像&&方法一样被修改;它毕竟是const的(如前所述,mutable可以改变这种情况)。因此,我们不能像在普通的move中那样将其拆开,因为临时变量 anyway 正在过期。

从很多方面来看,const&&的有用性可能最好在评估类型为const T&&的对象的有用性时进行。 const T&&函数参数有多有用?正如另一个答案(对于这个问题)here中指出的那样,它们非常有用,可以声明函数为已删除的,例如在这种情况下。

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

为了明确地禁止使用prvalue和xvalue值类别类型的对象与函数一起使用,const T&&绑定到所有prvalue和xvalue对象

const&&方法限定符有什么用?

值得注意的是,在提议C++库扩展的§5.3中,optional包括重载,例如:
constexpr T value() const &&;

这些被标记为const&&并指定执行与&&替代方法相同操作的内容是合格的。

我可以推断出这种情况的原因是为了完整性和正确性。如果在rvalue上调用value()方法,则无论其是否为const,它都会执行相同的操作。需要通过移动包含对象或使用它的客户端代码来处理const。如果对象中包含一些可变状态,则可以合理地更改该状态。

这仍然可能有一些优点,没有特定的顺序...

  • 声明= delete以禁止在prvalues和xvalues上使用该方法。
  • 如果类型具有可变状态并且限定符在目标环境中有意义(可能还要添加其他限定符),则考虑使用它。
  • 如果您正在实现通用容器类型,则出于完整性和正确性的考虑,请考虑添加它并执行与&&方法相同的操作。建议从标准库(及其扩展)中获取建议。
这是一个关于编程的问题,问如何为一个带有const&&限定符的方法创建“规范”的签名。考虑到该方法将执行与&&方法相同的操作,我建议签名与&&签名匹配。

1
如果您正在实现一个通用容器类型,那么为了完整性和正确性,请考虑添加它并执行与“&&”方法相同的操作。但是,由于容器及其内容应视为const,所以无法这样做。对于const右值对象访问,将调用const &限定的方法。 - Potatoswatter
@Potatoswatter。这是在C++库扩展中为optional等指定的。&&const&&方法具有相同的效果(n4082,第5.3.5段第11、20等)。值得注意的是,GCC在给定const rvalue时选择正确方法存在问题,但clang没有。我认为这里的重点是一般情况下没有用处,但还有一些特殊情况需要考虑(然后仔细考虑为什么需要它)。 - Niall

12

我们可以在文章"const rvalue references有什么作用?"中找到一个类似的探讨这个问题的内容。其中突出的一个用途是标准库中的一个例子:

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

该选项完全禁用了rvalue的refcref。我们可以在C++11标准草案20.8Function objects2段中找到这些声明。

Scott Meyers在《C++11中的通用引用》中提到了这种用法:

即使只是添加一个const限定符,也足以禁用将“&&”解释为通用引用:


我认为我基本上同意这篇文章,尤其是关于ref-qualified方法的部分,除了禁止使用之外,几乎没有其他用途。 - Niall
请注意,有多个相关的问题在SO上早于该文章。例如,https://dev59.com/PW445IYBdhLWcg3wTIZf - Ben Voigt
@BenVoigt 有几个答案,但我只看了第一个搜索结果,因为它非常好地回答了问题,并且以一种非常相似的方式清晰地探讨了这个问题。然而,这个问题是迄今为止最优质的。 - Shafik Yaghmour
引用限定符从来不表示通用引用。我不明白这个答案与问题有什么关系。 - Potatoswatter
你的回答不是很清楚。const T&& 真的只接受右值吗?为什么不只实现 template <class T> void ref (const T&) 呢?这样,尝试传递右值将会失败。如果你所写的是真的,那么使用 const T&& 是愚蠢的。 - BЈовић
@BЈовић,我之前问过类似的问题https://dev59.com/loHba4cB1Zd3GeqPWtmS - Niall

5
假设我们有一个带有可变状态的类型。然后,const&&既允许我们修改该状态,又表明这种修改是安全的。
struct bar;

struct foo {
  mutable std::vector<char> state;
  operator bar() const&;
  operator bar() const&&;
};

const并非绝对的。

除了可变状态外,在const&&方法中强制转换const以提取状态是不安全的,因为以这种方式从实际const对象中提取状态是未定义的行为。


mutable 在 C++11 中为编程带来了非常有趣的东西,因为它还涉及到线程安全。 - Niall
只有在std容器或由std提供的类型中才能使用@niall。 - Yakk - Adam Nevraumont
那是一个很好的观点。从技术上讲,我同意。但从个人角度来看,我通常希望类型能够与标准库一起使用和使用。鉴于一般实现中发现的当前符合水平,我认为我们都可以或应该这样做 - 如果不是这种情况,那将是违反直觉的。我认为标准库为我们提供了一个相当好的模型来遵循。我也理解,这并不总是适用的 - 但我认为这些情况是有限的(我知道一些)。 - Niall

2
我认为对于ref-qualifying方法有两个主要用途。一个是像你在get() &&方法中展示的那样,可以使用它来选择可能更有效的实现,只有当你知道对象将不再使用时才可用。但另一个则是安全提示,防止在临时对象上调用某些方法。
在这种情况下,您可以使用get() const && = delete等符号,但实际上我建议保存此方法来修改方法,特别是那些可能昂贵的方法。毫无意义的是,在突变和丢弃对象而没有检索任何内容的情况下,如果执行变异是昂贵的,则这样做加倍。这个结构使编译器有一种方法来标记和防止这种使用方式。

=delete 让你对对象不能使用的方式有很多控制,我认为这可能是 const&& 的通用用法(如果有的话)。 - Niall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接