移动构造函数在什么情况下被调用,而不是复制构造函数?

huangapple go评论71阅读模式
英文:

When does the move constructor get called rather than the copy constructor?

问题

在你提供的代码中,你有一个名为MemoryBlock的类,其中包括拷贝构造函数(Copy ctor)和移动构造函数(Move ctor)。在主函数main中,你创建了一个std::vector<MemoryBlock>,并尝试向它添加MemoryBlock对象。

现在,让我来解释一下为什么在下面的情况中会调用移动构造函数而不是拷贝构造函数:

v.push_back(MemoryBlock(25));

在这里,MemoryBlock(25) 创建了一个临时对象,它是一个右值(rvalue)。C++中,右值是临时的、即将销毁的值,可以通过移动语义来转移资源,而不是复制资源。

当你调用v.push_back(MemoryBlock(25))时,编译器会触发移动语义,因为它知道MemoryBlock(25)是一个右值,即将被销毁。因此,它调用了移动构造函数,将临时对象的资源(在这种情况下是m_data指向的内存块)转移到了v中的元素,而不是创建一个新的副本。这提高了性能,因为不需要额外的内存复制。

如果你明确使用std::move,它会告诉编译器强制执行移动语义,而不管是否是右值。例如:

v.push_back(std::move(m));

在这种情况下,即使m是一个左值(lvalue),std::move 也会告诉编译器执行移动构造函数。

希望这解释清楚了为什么在你的代码中会调用移动构造函数而不是拷贝构造函数。

英文:

I read a Microsoft tutorial (located here: https://learn.microsoft.com/en-us/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=msvc-170) about the move constructor and the move assignment operator and I didn't quite understand when the move ctor is invoked.

Below the code of the MemoryBlock class

class MemoryBlock
{
public:
    explicit MemoryBlock(size_t length)
        : m_length(length),
          m_data(new int[length])
    {
        std::cout &lt;&lt; &quot;In MemoryBlock(size_t). length = &quot;
                  &lt;&lt; m_length &lt;&lt; &quot;.&quot; &lt;&lt; std::endl;
    }

    virtual ~MemoryBlock()
    {
        std::cout &lt;&lt; &quot;In ~MemoryBlock(). length = &quot;
                  &lt;&lt; m_length &lt;&lt; &quot;.&quot;;

        if (m_data != nullptr)
        {
            delete[] m_data;
            m_data = nullptr;
        }

        std::cout &lt;&lt; std::endl;
    }

    // Copy ctor
    MemoryBlock(const MemoryBlock&amp; other)
        : m_length(other.m_length),
          m_data(new int[other.m_length])
    {
        std::cout &lt;&lt; &quot;In MemoryBlock(const MemoryBlock&amp;). length = &quot;
                        &lt;&lt; other.m_length &lt;&lt; &quot;. Copying resource.&quot; &lt;&lt; std::endl;

        std::copy(other.m_data, other.m_data + m_length, m_data);
    }

    // Copy assignment operator
    MemoryBlock&amp; operator=(const MemoryBlock&amp; other)
    {
        std::cout &lt;&lt; &quot;In operator=(const MemoryBlock&amp;). length = &quot;
                        &lt;&lt; other.m_length &lt;&lt; &quot;. Copying resource.&quot; &lt;&lt; std::endl;

        if (this != &amp;other)
        {
            delete[] m_data;

            m_length = other.m_length;
            m_data = new int[m_length];

            std::copy(other.m_data, other.m_data + m_length, m_data);
        }

        return *this;
    }

    // Move ctor
    MemoryBlock(MemoryBlock&amp;&amp; other) noexcept
        : m_length(0),
          m_data(nullptr)
    {
        std::cout &lt;&lt; &quot;In MemoryBlock(MemoryBlock&amp;&amp;). length = &quot;
                     &lt;&lt; other.m_length &lt;&lt; &quot;. Moving resource.&quot; &lt;&lt; std::endl;

        m_data = other.m_data;
        m_length = other.m_length;

        other.m_data = nullptr;
        other.m_length = 0;
    }

    // Move assignment
    MemoryBlock&amp; operator=(MemoryBlock&amp;&amp; other) noexcept
    {
        // Avoid assigning the object to itself
        if (this != &amp;other)
        {
            // Free the existing resource
            delete[] m_data;

            m_data = other.m_data;
            m_length = other.m_length;

            other.m_data = nullptr;
            other.m_length = 0;
        }

        return *this;
    }

private:
    size_t m_length;
    int *m_data;
};

What I don't I understand is in the main.
Understand why the move ctor is invoked rather than the copy ctor

int main()
{
    std::vector&lt;MemoryBlock&gt; v;

    // In this way the copy ctor is called
    MemoryBlock m(10);
    v.push_back(m);

    // And in this way the move ctor is called, why if std::move is not invoked?
    v.push_back(MemoryBlock(25));

    return 0;
}

I compiled and built the code and tried to understand, also searching for information in internet without any result

答案1

得分: 1

MemoryBlock&& 是一个 rvalue reference。因此,移动构造函数:

MemoryBlock(MemoryBlock&& other) noexcept

在你传递一个 rvalue 时被调用。有许多方法可以创建一个 rvalue。std::move 只是其中之一。其他方式,就像在你的情况中一样,是创建一个无名临时对象。这也可以通过一个函数来实现:

MemoryBlock foo() { ... }

...

MemoryBlock m{foo()};

所有这些都是 rvalues,因此移动构造函数被调用。

英文:

MemoryBlock&amp;&amp; is an rvalue reference. So the move constructor:

MemoryBlock(MemoryBlock&amp;&amp; other) noexcept

Is invoked whenever you pass it an rvalue. There are lots of ways to make an rvalue. std::move is only one of those ways. Others, like in your case, are creating an unnamed temporary. This can also be done with a function:

MemoryBlock foo() { ... }

...

MemoryBlock m{foo()};

All of these are rvalues, so the move constructor is called.

答案2

得分: 1

这是因为当你写 v.push_back(m); 时,表达式 m 具有 lvalue 的值类别,因此使用了 std::vector::push_backlvalue 引用 版本 push_back( const T& ),它使用了 拷贝初始化

另一方面,当你写 v.push_back(MemoryBlock(25)); 时,表达式 MemoryBlock(25) 是一个 rvalue,因此使用了 std::vector::push_backpush_back( T&& value ) 版本,因此这个版本使用了移动构造函数。

来自 std::vector::push_back

void push_back( const T& value );      (1)
void push_back( T&& value );           (2)

将给定的元素值追加到容器的末尾。

  1. 新元素被初始化为 value 的拷贝
  2. value 被 移动到 新元素中。
英文:

This is because when you wrote v.push_back(m);, the expression m has the value category of lvalue and so the lvalue reference version push_back( const T&amp; ) of std::vector::push_back is used which uses copy initialization.

On the other hand, when you wrote v.push_back(MemoryBlock(25));, the expression MemoryBlock(25) is an rvalue and so the push_back( T&amp;&amp; value ) version of std::vector::push_back is used. So this version uses the move constructor.

From std::vector::push_back:

>
&gt; void push_back( const T&amp; value ); (1)
&gt; void push_back( T&amp;&amp; value ); (2)
&gt;

>
> Appends the given element value to the end of the container.
> 1) The new element is initialized as a copy of value.
> 2) value is moved into the new element.

答案3

得分: 0

在一般情况下:

  • 当使用 rvalues 创建新对象时,会调用移动构造函数。
  • 当使用 lvalue 创建新对象时,会调用复制构造函数。
  • 当右操作数对象是 rvalues 时,会调用移动赋值运算符。
  • 当右操作数对象是 lvalues 时,会调用赋值运算符。

关于你的问题:“我不理解的是在主函数中为什么会调用移动构造函数而不是复制构造函数”

v.push_back(m); 这一行中,m 是一个 lvalue,因此调用了复制构造函数。而在 v.push_back(MemoryBlock(25)); 这一行中,MemoryBlock(25) 是一个 rvalue,因此调用了移动构造函数。

以下是一些在这个上下文中有用的阅读材料:

英文:

In general:

  • Move constructor is invoked when rvalues is used to create a new object
  • Copy constructor is invoked when lvalue is used to create a new object
  • Move assignment operator is invoked when left right-hand-side object is of rvalues
  • Assignment operator is invoked when left right-hand-side object is of lvalues

Coming to your question: What I don't I understand is in the main. Understand why the move ctor is invoked rather than the copy ctor

In line v.push_back(m);, m is an lvalue so copy-constructor is called. While in line v.push_back(MemoryBlock(25));, MemoryBlock(25) is an rvalue so move-constructor is called.

Following are some useful reading in this context:

huangapple
  • 本文由 发表于 2023年3月1日 12:07:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/75599489.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定