【问题标题】:Ramification of assignment operators with values instead of references赋值运算符的分支与值而不是引用
【发布时间】:2012-04-02 14:16:18
【问题描述】:

这个问题来自this answer提出的问题。

通常,我们将T 类型的复制赋值运算符定义为T& operator=(const T&),并将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 等特征类有何影响?

【问题讨论】:

  • 如果我同时有 T& operator = (T t) 和 T& operator = (T && t) 的东西,我会在 Visual Studio 和 g++ 上遇到编译器错误,因为它不明确
  • @user929404:你应该这样做。关键是你用值赋值替换both复制和移动赋值。

标签: c++ c++11 language-lawyer move-semantics


【解决方案1】:

第 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 没有用户声明的复制赋值运算符,
[...]

由于该类具有用户声明的复制赋值运算符,因此编译器不会生成任何隐式赋值运算符。

std::is_copy_assignablestd::is_move_assignable 在表 49 中被描述为分别与 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&&>() 对于该类都是格式良好的,它仍然算作可复制分配和可移动分配。

正如我在 cmets 中提到的,对这一切感到好奇的是,在存在移动构造函数的情况下,operator= 将正确执行移动,但在技术上不算作移动赋值运算符。如果该类没有复制构造函数,那就更奇怪了:它将有一个不进行复制,而只是移动的复制赋值运算符。

【讨论】:

    猜你喜欢
    • 2022-01-07
    • 2020-08-10
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 2017-11-21
    • 2020-08-17
    相关资源
    最近更新 更多