在引用限定符上重载成员函数的用例是啥?

Posted

技术标签:

【中文标题】在引用限定符上重载成员函数的用例是啥?【英文标题】:What's a use case for overloading member functions on reference qualifiers?在引用限定符上重载成员函数的用例是什么? 【发布时间】:2014-01-29 21:38:51 【问题描述】:

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

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 建议将标准库中的大多数赋值运算符限制为左值目标(即,将“&”引用限定符添加到赋值运算符),但 this was rejected。所以这是一个潜在的用例,委员会决定不使用它。那么,什么是合理的用例呢?

【问题讨论】:

【参考方案1】:

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

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

编辑:我找到了original proposal paper,它提供了三个激励示例:

    operator = 限制为左值(TemplateRex 的答案) 为成员启用移动(基本上是这个答案) 将operator & 约束为左值。我认为当“指针”最终被取消引用时,确保“指针”更有可能存活是明智的:
struct S 
  T operator &() &;
;

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

不能说我曾经亲自使用过operator& 重载。

【讨论】:

huge_heavy_class&& get() && 错误(导致引用悬空,应该是 huge_heavy_class get() &&),auto hhc = std::move(factory().get()); 将是多余的。 @ildjarn 为什么你关心返回一个右值引用,而不是返回一个左值引用?一个人不会比另一个人更容易晃来晃去。是的,这篇文章的重点是要编写很多代码以避免需要编写 std::move(factory().get()) - 我想我需要更清楚地说明这一点。 因为左值引用只为左值返回,因此不是问题... 我将其标记为答案,因为它很好地总结了情况,并且链接到了原始提案。【参考方案2】:

一个用例是prohibit assignment to temporaries

 // 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 在 cmets 中的链接还提供了关于使用其他 (&&) 重载的扩展讨论,如果您想分配给(左值或右值),这可能很有用参考文献。

【讨论】:

更一般地说:防止暴露对临时内部数据的引用或指针。 你什么时候希望它们是可分配的? @Mehrdad 我不知道,但如果你不指定&,他们会的。 @TemplateRex:很有趣。我觉得这可以通过完全禁止通过= rhs 分配右值来解决,而不是通过引入这个更通用的构造。如果人们真的想分配给右值,他们可以改用.operator=(rhs) @Mehrdad 以使编译器编写者的生活复杂化为代价可能并不令人惊讶 :-)【参考方案3】:

如果 f() 需要一个 Foo 临时文件,它是它的副本并已修改,您可以修改临时文件 this,否则您不能这样做

【讨论】:

【参考方案4】:

一方面,您可以通过添加& 作为引用限定符。

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

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

TL;DR:使用它来防止函数的错误使用或优化。

【讨论】:

以上是关于在引用限定符上重载成员函数的用例是啥?的主要内容,如果未能解决你的问题,请参考以下文章

NuxtJs asyncData 的用例是啥?

DataSet Spark 的用例是啥?

Ruby 的 %q / %Q 引用方法的用例是啥?

可以显式默认/删除的函数在 ref 限定符上重载吗?

将 std::forward_as_tuple() 结果传递给可能从该对象的右值引用成员移动的多个函数?

target_include_directories 上的生成器表达式的用例是啥?