英文:
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 << "In MemoryBlock(size_t). length = "
<< m_length << "." << std::endl;
}
virtual ~MemoryBlock()
{
std::cout << "In ~MemoryBlock(). length = "
<< m_length << ".";
if (m_data != nullptr)
{
delete[] m_data;
m_data = nullptr;
}
std::cout << std::endl;
}
// Copy ctor
MemoryBlock(const MemoryBlock& other)
: m_length(other.m_length),
m_data(new int[other.m_length])
{
std::cout << "In MemoryBlock(const MemoryBlock&). length = "
<< other.m_length << ". Copying resource." << std::endl;
std::copy(other.m_data, other.m_data + m_length, m_data);
}
// Copy assignment operator
MemoryBlock& operator=(const MemoryBlock& other)
{
std::cout << "In operator=(const MemoryBlock&). length = "
<< other.m_length << ". Copying resource." << std::endl;
if (this != &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&& other) noexcept
: m_length(0),
m_data(nullptr)
{
std::cout << "In MemoryBlock(MemoryBlock&&). length = "
<< other.m_length << ". Moving resource." << std::endl;
m_data = other.m_data;
m_length = other.m_length;
other.m_data = nullptr;
other.m_length = 0;
}
// Move assignment
MemoryBlock& operator=(MemoryBlock&& other) noexcept
{
// Avoid assigning the object to itself
if (this != &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<MemoryBlock> 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&&
is an rvalue reference. So the move constructor:
MemoryBlock(MemoryBlock&& 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_back
的 lvalue 引用 版本 push_back( const T& )
,它使用了 拷贝初始化。
另一方面,当你写 v.push_back(MemoryBlock(25));
时,表达式 MemoryBlock(25)
是一个 rvalue,因此使用了 std::vector::push_back
的 push_back( T&& value )
版本,因此这个版本使用了移动构造函数。
void push_back( const T& value ); (1) void push_back( T&& value ); (2)
将给定的元素值追加到容器的末尾。
- 新元素被初始化为 value 的拷贝。
- 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& )
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&& value )
version of std::vector::push_back
is used. So this version uses the move constructor.
From std::vector::push_back:
>
> void push_back( const T& value ); (1)
> void push_back( T&& value ); (2)
>
>
> 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
,因此调用了移动构造函数。
以下是一些在这个上下文中有用的阅读材料:
- std::vector<T,Allocator>::push_back
- 值类别 (Value categories)
- 什么是 rvalues、lvalues、xvalues、glvalues 和 prvalues?
英文:
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:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论