赋值运算符中使用值而不是引用的影响分析

14
这个问题源于这个答案中提出的问题。
通常情况下,我们为类型T定义复制赋值运算符为T& operator=(const T&),移动赋值运算符为T& operator=(T&&)
但是,当我们使用值参数而不是引用时会发生什么?
class T
{
public:
  T& operator=(T t);
};

这应该使得T既能够进行复制赋值,又能够进行移动赋值。然而,我想知道的是,对于T这种情况,语言方面会有什么影响呢?

具体来说:

  1. 根据规范,这是否算作T的复制赋值运算符?
  2. 根据规范,这是否算作T的移动赋值运算符?
  3. T是否会有编译器生成的复制赋值运算符?
  4. T是否会有编译器生成的移动赋值运算符?
  5. 这将如何影响像std::is_move_assignable这样的特性类?

相关讨论:http://cpp-next.com/archive/2009/08/want-speed-pass-by-value请仅返回翻译后的文本。 - mavam
如果我同时拥有T&operator =(T t)和T&operator =(T && t),则无论是在Visual Studio还是g ++上,我都会遇到编译器错误,因为它是模棱两可的。 - user929404
@user929404:而且你应该这样做。重点是用值赋值来替换复制和移动赋值 - Nicol Bolas
1个回答

14
大部分内容在§12.8中有描述。第17段定义了什么是用户声明的复制赋值运算符:

用户声明的复制赋值运算符X::operator=是类X的非静态非模板成员函数,它只有一个参数,类型为XX&const X&volatile X&const volatile X&

第19段定义了什么是用户声明的移动赋值运算符:

用户声明的移动赋值运算符X::operator=是类X的非静态非模板成员函数,它只有一个参数,类型为X&&const X&&volatile X&&const volatile X&&

因此,它被视为复制赋值运算符,但不是移动赋值运算符。
第18段告诉我们编译器何时生成复制赋值运算符:

如果类定义没有显式声明复制赋值运算符,则会隐式声明一个。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制赋值运算符将被定义为已删除;否则,它将被定义为默认值(8.4)。如果类具有用户声明的复制构造函数或用户声明的析构函数,则后一种情况已被弃用。

第20段告诉我们编译器何时生成移动赋值运算符:

如果类X的定义没有显式声明移动赋值运算符,则当且仅当
[...]
— X没有用户声明的复制赋值运算符时,
[...]
将隐式声明为默认值。

由于该类具有用户声明的复制赋值运算符,因此编译器不会生成任何隐式的赋值运算符。
表49中描述了std::is_copy_assignablestd::is_move_assignable具有相同的值,分别为is_assignable<T&,T const&>::valueis_assignable<T&,T&&>::value。该表告诉我们,当以下条件满足时,is_assignable<T,U>::valuetrue

将表达式declval<T>() = declval<U>()视为未评估的操作数(第5条)时,它是良好的形式。访问检查被执行,就像在与TU无关的上下文中一样。只考虑赋值表达式的直接上下文的有效性。

由于对于该类,declval<T&>() = declval<T const&>()declval<T&>() = declval<T&&>()都是良好的形式,因此它仍然可以被视为可复制和可移动。

正如我在评论中提到的那样,在移动构造函数存在的情况下,operator=将正确执行移动操作,但从技术上讲并不算作移动赋值运算符。如果该类没有复制构造函数,那么情况就更奇怪了:它将具有一个不执行复制而只执行移动的复制赋值运算符。


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