我应该在赋值运算符中使用左值引用限定符吗?

17

最近,我关注了一个有关C ++中表达式赋值的讨论,如下例所示:

string s1, s2, s3;
(s1 + s2) = s3;

在C++11中,可以将赋值运算符限制为左值引用(在左侧)。声明赋值运算符如下所示时,编译器Clang由于不兼容的类型而拒绝代码,并显示错误消息。

auto operator=(const string& rhs) & -> string&;
auto operator=(string&& rhs) & -> string&;

我在任何地方都没有看到这个问题。除了大多数编译器不支持以外,是否存在不使用lvalue引用限定符进行赋值运算符的好理由?


5
其中一个原因是大多数编译器还不支持这种语法。另一个原因是它并没有解决一个重大的问题。这种错误有多常见? - Bo Persson
1
我认为这种情况确实会发生。if (somefunc() = value) 当然,大多数编译器都会对此发出警告,但并非所有情况都是如此。 - Tamás Szelei
3个回答

8
有趣!我甚至没有意识到这一点,并花了一段时间才找到它(它是{{link1:"将移动语义扩展到*this"}提案的一部分)。如果有人想查看,符号已在8.3.5 [dcl.decl]第4段中定义。
无论如何:现在,知道了这个功能,似乎最有用的是用它来重载,并且可能会根据调用函数的对象是左值还是右值而表现出不同的行为。使用它来限制可以做什么,例如,使用赋值结果似乎是不必要的,特别是如果对象实际上是左值。例如,您可能希望语法从对右值进行赋值返回一个右值:
struct T {
    auto operator=(T&) & -> T&;
    auto operator=(T&&) & -> T&;
    auto operator=(T&) && -> T;
    auto operator=(T&&) && -> T;
};

本意是使移动赋值结果变得可行(虽然我不确定这样做是否值得,为什么不一开始就跳过赋值呢?)。我认为主要是喜欢时不时地获取 lvalue,而赋值运算符通常是这样做的一种方式。例如,如果需要将 lvalue 传递给函数,但你知道自己不想使用它,可以使用赋值运算符获得一个 lvalue:

#include <vector>
void f(std::vector<int>&);
int main()
{
    f(std::vector<int>() = std::vector<int>(10));
}

这可能是滥用赋值运算符以从rvalue获取lvalue,但不太可能意外发生。因此,我不会刻意限制赋值运算符仅适用于lvalue,以防止这种情况发生。当然,从rvalue分配到rvalue也会防止这种情况发生。如果有的话,哪种用法更有用可能需要考虑。
顺便说一下,clang似乎支持您引用的语法自版本2.9起。

6
我宁愿有一个清晰的 template<class T> T& as_lvalue(T&& v){ return v; } 函数,也不想让我的同行们感到困惑,不知道你试图通过赋值来实现什么(对于不了解移动语义的人来说,这似乎还包括一个无用的 vector 复制)。 - Xeo

4
除了大多数编译器不支持(rvalue refs for *this 类似于 thread_local,大多数编译器实现者似乎把它放在“从 C++11 实现的特性”堆栈的底部),还有其他很好的理由不使用 lvalue 引用限定符作为赋值运算符吗?其实没有。使用 lvalue 或 rvalue 限定符来构造正确的 lvalue 或 rvalue 对象接口与使用 const 的方式是一样的,应该以相同的方式来处理 —— 每个函数都应该被考虑为受限制的。将一个 rvalue 赋值并没有太多意义,所以应该禁止这样做。

在我看来,这是最好的答案。虽然我更感兴趣的是赋值运算符,因为它是一个特殊的成员函数。使用引用限定符可能会对Rule-of-5、继承、成员变量作为聚合体或与标准容器和标准库一起使用产生一些影响。不过,我本应该询问这些信息。 - nosid
2
阅读此答案时请注意。如果您只阅读问题的标题,您可能会认为“不,实际上不是”回答了问题的标题。它回答了问题的结尾处的问题:“不使用lvalue引用限定符作为赋值运算符是否有充分的理由?” - Miles Rout
还要注意的是,自回答发布以来,提到的编译器支持已经有了很大的改进。截至2019年,几乎所有主流和次要编译器都支持ref-qualifiers。 - Miles Rout

4
我不是特别热衷于您的建议的原因之一是我试图避免完全声明特殊成员。因此,我的大多数赋值运算符都是隐式声明的,因此没有ref-qualifiers。
当然,对于那些我编写类或类模板来管理所有权的时候(请参见上面链接中的结论),我可以注意仅为左值声明这些运算符。但是,由于它对客户端没有影响,所以并没有太大意义。

我同意你的看法。如果有可能,我会避免声明这五个中的任何一个。然而,编译器生成的赋值运算符并不仅限于左值引用。很可能是为了向后兼容。 - nosid

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