在引用限定符上重载成员函数有什么用途?

31

C++11使得基于引用限定符(reference qualifiers)重载成员函数成为可能:

class Foo {
public:
  void f() &;   // for when *this is an lvalue
  void f() &&;  // for when *this is an rvalue
};

Foo obj;
obj.f();               // calls lvalue overload
std::move(obj).f();    // calls rvalue overload

我了解这是如何工作的,但它有什么用例?

我看到N2819提议将标准库中的大多数赋值运算符限制为lvalue目标(即在赋值运算符中添加"&"引用限定符),但这被拒绝了。所以这是一个委员会决定不采用的潜在用例。那么,什么是合理的用例?

4个回答

25

在提供引用获取器的类中,使用引用限定符重载可以在从 rvalue 中提取时激活移动语义。例:

class some_class {
  huge_heavy_class hhc;
public:
  huge_heavy_class& get() & {
    return hhc;
  }
  huge_heavy_class const& get() const& {
    return hhc;
  }
  huge_heavy_class&& get() && {
    return std::move(hhc);
  }
};

some_class factory();
auto hhc = factory().get();

这似乎需要花费很多精力,仅为了拥有更短的语法。
auto hhc = factory().get();

具有相同的效果如

auto hhc = std::move(factory().get());

编辑:我找到了原始提案文件,它提供了三个激励示例:

  1. operator =约束为左值(TemplateRex的答案)
  2. 启用成员移动(基本上是这个答案)
  3. operator &约束为左值。我认为这是明智的,以确保“指针”最终被取消引用时,“指向”的对象更有可能存在:
struct S {
  T operator &() &;
};

int main() {
  S foo;
  auto p1 = &foo;  // Ok
  auto p2 = &S();  // Error
}

我个人并没有使用过operator&运算符重载。


1
huge_heavy_class&& get() && 是错误的(会导致悬空引用),应该改为 huge_heavy_class get() &&。此外,auto hhc = std::move(factory().get()); 是多余的。 - ildjarn
1
@ildjarn,你为什么关心返回一个右值引用,而不是返回左值引用?两者都不太可能悬空。是的,这篇文章的重点是为了避免写std::move(factory().get())而需要编写大量代码 - 我想我需要让这一点更加清晰明了。 - Casey
因为lvalue引用仅返回给lvalue,因此不会出现问题... - ildjarn
我将此标记为答案,因为它很好地总结了情况,并链接到原始提案。 - KnowItAllWannabe

14

一个使用案例是禁止将值赋给临时变量

 // can only be used with lvalues
 T& operator*=(T const& other) & { /* ... */ return *this; } 

 // not possible to do (a * b) = c;
 T operator*(T const& lhs, T const& rhs) { return lhs *= rhs; }

如果不使用引用限定符,你只能在两种坏选择之间做出选择。

       T operator*(T const& lhs, T const& rhs); // can be used on rvalues
 const T operator*(T const& lhs, T const& rhs); // inhibits move semantics

第一种选择允许使用移动语义,但在用户定义类型上的行为与基本内置类型不同(不像int那样)。第二种选择将停止赋值操作,但会消除移动语义(例如矩阵乘法可能会造成性能损失)。
@dyp在评论中提供的链接还提供了有关使用另一个(&&)重载的扩展讨论,如果您想向引用(左值或右值)分配,则可以很有用。

更一般地说:防止暴露临时数据的内部引用或指针。 - dyp
你什么时候会想要它们是可分配的? - user541686
@Mehrdad 我不知道,但如果你不指定 &,它们就会被默认为引用传递。 - TemplateRex
@TemplateRex:有趣。我觉得这个问题本来可以通过完全禁止将rvalue通过= rhs赋值来解决,而不是引入这个更通用的结构。如果人们真的想要对rvalue进行赋值,他们只需使用.operator=(rhs)即可。 - user541686
@Mehrdad 这可能会让编译器编写者的生活更加复杂,但这并不令人意外。 - TemplateRex
@TemplateRex:我的意思是,由于这两个并不等价,他们的生活已经变得很复杂了。你不能执行 int.operator=(int),所以它不像他们可以盲目地将其转换为 int = int,他们最好也检查一下这个。 - user541686

3

如果f()需要一个复制了当前对象的Foo临时变量并对其进行修改,那么您可以修改该临时变量this,否则就无法修改。


0

一方面,您可以使用它们来防止在临时对象上调用语义上无意义的函数,例如operator=或会改变内部状态并返回void的函数,通过添加&作为引用限定符。

另一方面,您可以将其用于优化,例如当您拥有右值引用时,将成员移出对象作为返回值。例如,函数getName可以返回std::string const&std::string&&,具体取决于引用限定符。

另一个用例可能是返回对原始对象的引用的运算符和函数,例如Foo& operator+=(Foo&),可以专门返回右值引用,从而使结果可移动,这将再次进行优化。

简而言之:使用它来防止函数的错误使用或进行优化。


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