【问题标题】:Why move assignment in my class wasn't called?为什么没有调用我课堂上的移动作业?
【发布时间】:2021-12-22 10:46:44
【问题描述】:

考虑这段代码:

#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;

struct BigInteger
{
    vector<int> arr;

    BigInteger()
    {
        cout << "default constructor" << endl;
        this->arr.push_back(0);
    }
    BigInteger(initializer_list<int> il)
    {
        cout << "initializer_list constructor" << endl;
        for (const int x : il)
        {
            arr.push_back(x);
        }
    }
    BigInteger(const BigInteger& obj) //copy constructor
    {
        cout << "copy constructor" << endl;
        this->arr = obj.arr; 
    }
    BigInteger(BigInteger&& obj) //move constructor
    {
        cout << "move constructor" << endl;
        swap(*this, obj);
    }
    ~BigInteger() //destructor
    {
        cout << "destructor" << endl;
        arr.clear(); //probably because of RAII, I guess I don't have to write this
    }
    BigInteger& operator=(const BigInteger& rhs)
    {
        cout << "copy assignment" << endl;
        BigInteger tmp(rhs);
        this->arr = tmp.arr;
        return *this;
    }
    BigInteger& operator=(BigInteger&& rhs) noexcept 
    {
        cout << "move assignment" << endl;
        swap(*this, rhs);
        return *this;
    }
};

int main()
{
    BigInteger another = BigInteger({0, 1, 2});
}

所以,我正在创建一个临时对象BigInteger({0, 1, 2}),然后为我的类实例a(理论上)进行移动分配。所以预期的输出是:

initializer_list 构造函数 //创建临时对象

默认构造函数//创建glvalue(非临时)对象

移动作业

析构函数

但是输出是:

initializer_list 构造函数

析构函数

我什至不明白为什么会这样。我怀疑operator=和初始化不一样,但还是不明白我的对象是怎么构造的。

【问题讨论】:

  • 你说得对,赋值与初始化是分开的。语法中的= 是红鲱鱼。
  • @Frank 所以复制省略有点直接初始化我的数据成员?我可以以某种方式明确禁止复制省略吗?
  • @Learpcs 自 C++17 起,BigInteger another = BigInteger({0, 1, 2}); 被编译为好像它被写为BigInteger another({0, 1, 2});,所以没有临时的BigInteger 对象可以优化掉。你不能禁止它,你只能解决它,例如先将BigInteger({0, 1, 2})保存到另一个变量,或者将它移动到一个函数中,然后从该函数的返回中初始化another。但即便如此,复制省略仍将尝试优化 temp BigInteger 对象。您不能禁用复制省略,但在某些情况下有办法使其不可行。

标签: c++ oop move-semantics move-constructor copy-elision


【解决方案1】:

首先,不会调用移动赋值运算符,因为您没有分配任何内容。 Type name = ...; 是初始化,不是赋值。

此外,甚至没有移动构造,因为BigInteger({0, 1, 2}) 是与初始化对象相同类型的prvalue,因此不会实现临时对象,而是使用prvalue 的初始化器来初始化@直接987654323@,就好像你写了BigInteger another = {0, 1, 2};一样(顺便推荐你写,根本不用重复输入)。

我怀疑operator=和初始化不一样

这是正确的。赋值运算符和初始化是两个不同的东西。在此示例中,您没有使用赋值运算符。

【讨论】:

  • 所以,我正在使用初始化。但它是如何实现的?它使用复制省略吗?它是否使用我的复制/移动构造函数? (由于我的输出,我怀疑第三个问题的答案是否定的)
  • Does it use copy elision? 在 C++17 之前,它会(可能)使用复制省略优化。但是从 C++17 开始,它不再是优化,而是在语言中指定了行为。 Does it use my copy/move constructors?看我回答的第二段。
  • 我有一个关于BigInteger another = {0, 1, 2}; 行的问题。为什么它有效?它是否使用我的 initializer_list 构造函数?如果是,那么BigInteger another = {0, 1, 2};BigInteger another {0, 1, 2}; 在语义上是否相同?我没有实现operator=(initalizer_list&lt;int&gt; l)。 UPD:实际上我们不需要实现operator=(initalizer_list&lt;int&gt; l),因为正如我们所说,它不是初始化。
  • @Learpcs 是的,如果你有一个std::initializer_list 构造函数,那么一个大括号初始化器列表将调用该构造函数。是的,这两个在语义上是相同的。事实上,因为它不是一个赋值,所以不会调用operator=
  • @Learpcs "我没有实现operator=(initalizer_list&lt;int&gt; l)" - operator= 从未在初始化期间使用,仅在赋值期间使用BigInteger another = {0, 1, 2};初始化,而不是赋值。您需要了解两者之间的区别。 初始化 仅在创建新对象时使用。 Assignment 仅在更新现有对象时使用。
【解决方案2】:

所以,我正在创建一个临时对象 BigInteger({0, 1, 2}) 然后 为我的类实例 a 做移动分配(理论上)。

似乎您的意思是移动构造而不是移动赋值,因为您在谈论声​​明。

来自 C++ 14 标准(12.8 复制和移动类对象)

31 当满足某些条件时,允许省略实现 类对象的复制/移动构造,即使构造函数 选择用于复制/移动操作和/或析构函数 对象有副作用。在这种情况下,实施将 省略的复制/移动操作的源和目标只是两个 引用同一对象的不同方式,以及 该对象发生在两个对象的较晚时间 没有优化就会被破坏。 122 复制/移动操作,称为复制省略,在 以下情况(可以结合起来消除多个 副本):

(31.3) — 当临时类对象尚未绑定到 参考(12.2)将被复制/移动到具有相同的类对象 cv-unqualified 类型,复制/移动操作可以省略 将临时对象直接构造到 省略复制/移动

所以在这个声明中

BigInteger another = BigInteger({0, 1, 2});

通过将临时对象直接构造到省略的复制/移动的目标中来省略移动操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-21
    • 2014-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多