【问题标题】:Which to use: move assignment operator vs copy assignment operator使用哪个:移动赋值运算符与复制赋值运算符
【发布时间】:2014-11-06 05:10:52
【问题描述】:

我似乎不明白你为什么要使用move assignment operator

CLASSA & operator=(CLASSA && other); //move assignment operator

结束,copy assignment operator

CLASSA & operator=(CLASSA  other); //copy assignment operator

move assignment operator 仅采用 r-value reference,例如

CLASSA a1, a2, a3;
a1 = a2 + a3;

copy assignment operator 中,other 可以是使用copy constructormove constructor 的构造函数(如果other 使用右值初始化,则可以移动构造--如果move-constructor 已定义- -)。

如果是copy-constructed,我们将进行 1 份副本,并且该副本无法避免。

如果是move-constructed,则性能/行为与第一次重载产生的相同。

我的问题是:

1- 为什么要实现move assignment operator

2- 如果other 是从r 值构造的,那么编译器会选择调用哪个assignment operator?为什么?

【问题讨论】:

  • 1) 工作量少,2) 会模棱两可。
  • 所以你是说 2) 第一个重载会被调用?
  • CLASSA & operator=(CLASSA && other);move assignment operator。不知道这会如何改变您所询问的内容。复制赋值运算符采用CLASSAconst CLASSA&
  • @Kam:不。模棱两可的重载意味着当您尝试使用重载时重载解析失败,从而导致程序格式错误。
  • 性能/行为将不相同。如果您通过 r 值引用,可能会少走一步。这是否重要取决于移动CLASSA 的成本。

标签: c++ c++11 overload-resolution


【解决方案1】:

你不是在比较同类

如果您正在编写像 std::unique_ptr 这样的仅移动类型,那么移动赋值运算符将是您唯一的选择。

更典型的情况是你有一个可复制的类型,在这种情况下我认为你有三个选择。

  1. T& operator=(T const&)
  2. T& operator=(T const&)T& operator=(T&&)
  3. T& operator=(T) 移动

请注意,将您建议的两个重载放在一个类中不是一种选择,因为它会模棱两可。

选项 1 是传统的 C++98 选项,在大多数情况下都能正常运行。但是,如果您需要针对 r 值进行优化,您可以考虑选项 2 并添加移动赋值运算符。

考虑选项 3 和按值传递然后移动是很诱人的,我认为这就是您的建议。在这种情况下,您只需编写一个赋值运算符。它接受 l 值,并且仅以额外的一步为代价接受 r 值,许多人会提倡这种方法。

然而,Herb Sutter 在他的 "Back to the Basics! Essentials of Modern C++ Style" 在 CppCon 2014 上的演讲中指出,这个选项是有问题的并且可能会慢得多。在左值的情况下,它将执行无条件复制并且不会重用任何现有容量。他提供了数字来支持他的说法。唯一的例外是构造函数,它们没有现有的重用能力,而且您通常有很多参数,因此按值传递可以减少所需的重载次数。

因此,如果您需要针对 r 值进行优化,我建议您从选项 1 开始,然后转到选项 2。

【讨论】:

    【解决方案2】:

    显然,这两个重载并不等价:

    1. 采用右值引用的赋值运算符仅适用于表达式右侧的右值。为了也支持左值,可复制类型需要另一个重载,例如使用T const&。当然,对于仅移动类型,例如 std::unique_ptr<T>,定义此赋值运算符是合适的选择。
    2. 假设所讨论的类型既可复制又可移动构造,取值的赋值运算符涵盖右值和左值赋值。它的规范实现是调用swap() 将对象的状态替换为右侧的状态。它的优点是通常可以省略参数的复制/移动构造。

    无论如何,您都不希望在一个类中同时拥有两个重载!显然,从左值分配时,将选择取值的版本(其他选项不可行)。但是,在分配右值时,这两个赋值运算符都是可行的,即会有歧义。这可以通过尝试编译此代码来轻松验证:

    struct foo
    {
        void operator=(foo&&) {}
        void operator=(foo) {}
    };
    
    int main()
    {
        foo f;
        f = foo();
    }
    

    要分别处理移动和复制构造,您可以使用T&&T const& 作为参数定义一对赋值运算符。但是,这导致必须实现本质上相同的复制分配的两个版本,而只有 T 作为参数只需要实现一个复制分配。

    因此,有两个明显的选择:

    1. 对于只移动类型,您需要定义 T::operator= (T&&)
    2. 对于可复制类型,您需要定义 T::operator=(T)

    【讨论】:

    • Herb sutter points out 以这种方式使用按值传递可能比通过引用到常量传递要慢得多,因为它是无条件复制而不是重用现有容量。
    猜你喜欢
    • 2017-11-21
    • 2015-06-23
    • 2020-05-16
    • 2017-04-19
    • 2011-11-16
    • 1970-01-01
    • 1970-01-01
    • 2011-08-02
    • 2019-10-06
    相关资源
    最近更新 更多