在C++中移动构造函数和复制构造函数

move constructor and copy constructor in C++

本文关键字:构造函数 复制 移动 C++      更新时间:2023-10-16

我的理解是,当我们从函数返回本地对象时,如果存在移动构造函数,则会调用它。但是,我遇到了调用复制构造函数的情况,如函数foo2()中的以下示例所示。为什么会这样?

#include <cstdio>
#include <memory>
#include <thread>
#include <chrono>
class tNode
{
public:
tNode(int b = 10)
{
a = b;
printf("a: %d, default constructor %s() is called at %s:%d n", a, __func__, __FILE__, __LINE__);
}
tNode(const tNode& node)
{
a = node.a;
printf("a: %d, copy constructor %s() is called at %s:%d n", a, __func__, __FILE__, __LINE__);
}
tNode& operator=(const tNode& node)
{
a = node.a;
printf("a: %d, copy assignment %s() is called at %s:%d n", a, __func__, __FILE__, __LINE__);
}
tNode(tNode&& node)
{
a = node.a;
printf("a: %d, move constructor %s() is called at %s:%d n", a, __func__, __FILE__, __LINE__);
}
tNode& operator=(tNode&& node)
{
a = node.a;
printf("a: %d, move assignment %s() is called at %s:%d n", a, __func__, __FILE__, __LINE__);
}
~tNode() { printf("a: %d, destructor %s() is called at %s:%d n", a, __func__, __FILE__, __LINE__); }
private:
int a = 0;
};
tNode foo()
{
tNode node;
return node;
}
tNode foo2()
{
std::unique_ptr<tNode> up = std::make_unique<tNode>(20);
return *up;
}
int main()
{
{
tNode n1 = foo();
tNode n2 = foo2();
}
// we pause here to watch how objects are created, copied/moved, and destroyed.
while (true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}

上面的代码是用g++ --std=c++17 -fno-elide-constructors编译的 输出为:

a: 10, default constructor tNode() is called at testCopyControl.cpp:13
a: 10, move constructor tNode() is called at testCopyControl.cpp:31
a: 10, destructor ~tNode() is called at testCopyControl.cpp:40
a: 10, move constructor tNode() is called at testCopyControl.cpp:31
a: 10, destructor ~tNode() is called at testCopyControl.cpp:40
a: 20, default constructor tNode() is called at testCopyControl.cpp:13
a: 20, copy constructor tNode() is called at testCopyControl.cpp:19
a: 20, destructor ~tNode() is called at testCopyControl.cpp:40
a: 20, move constructor tNode() is called at testCopyControl.cpp:31
a: 20, destructor ~tNode() is called at testCopyControl.cpp:40
a: 20, destructor ~tNode() is called at testCopyControl.cpp:40
a: 10, destructor ~tNode() is called at testCopyControl.cpp:40

从输出中,我们知道当foo2()返回*up初始化临时tNode对象时,会调用复制构造函数;为什么没有调用移动构造函数?

tNode foo()
{
tNode node;
return node;
}

tNode n1 = foo();

负责输出

a: 10,  tNode() is called at testCopyControl.cpp:13
a: 10,  move constructor tNode() is called at testCopyControl.cpp:31
a: 10, destructor ~tNode() is called at testCopyControl.cpp:40
a: 10,  move constructor tNode() is called at testCopyControl.cpp:31
a: 10, destructor ~tNode() is called at testCopyControl.cpp:40

你看到的是被调用的默认构造函数,然后nodereturn 语句中开始将其视为右值以将其移动到返回值中,然后从返回值移动到n1

tNode foo2()
{
std::unique_ptr<tNode> up = std::make_unique<tNode>(20);
return *up;
}

行为是不同的,因为您不返回函数本地对象。*up为您提供了一个tNode&,因此 return 语句不能将其视为右值。 由于它是一个左值,因此您必须调用复制构造函数才能将其复制到返回值中。 然后,与第一个示例一样,调用 move 构造函数将对象从返回值移动到n2中。

下面的代码不会隐式移动构造的对象:

tNode foo2()
{
std::unique_ptr<tNode> up = std::make_unique<tNode>(20);
return *up;
}

这是因为,无论在我们看来多么明显/直观,编译器都无法证明从up包含的对象中移动是安全的。它被迫通过复制返回。

您可以通过显式强制转换对象来强制它按 R 值返回:

tNode foo2()
{
std::unique_ptr<tNode> up = std::make_unique<tNode>(20);
return std::move(*up);
}