程序使用复制构造函数而不是移动构造函数
Posted
技术标签:
【中文标题】程序使用复制构造函数而不是移动构造函数【英文标题】:Program uses Copy Constructor instead of Move Constructor 【发布时间】:2021-12-25 12:01:29 【问题描述】:我试图理解 C++ 中复制和移动构造函数的概念。所以尝试不同的例子。下面给出了一个我无法理解其输出的示例:
#include <iostream>
#include <vector>
using namespace std;
struct NAME
NAME()
std::cout<<"default"<<std::endl;
NAME(const NAME& )
std::cout<<"const copy"<<std::endl;
NAME(NAME& )
std::cout<<"nonconst copy"<<std::endl;
NAME(NAME &&)
std::cout<<"move"<<std::endl;
;
void foo(std::pair<std::string, NAME> )
void foo2(std::vector<std::pair<std::string, NAME>> )
int main()
foo(std::make_pair("an", NAME())); //prints default --> move --> move
std::cout << "----------------------------------------"<<std::endl;
foo("an", NAME()); //prints default --> move
std::cout << "----------------------------------------"<<std::endl;
foo2("an", NAME()); //prints default --> move --> const copy
std::cout << "----------------------------------------"<<std::endl;
return 0;
案例1:对于foo(std::make_pair("an", NAME()));
输出
default
move
move
这就是我认为正在发生的事情。
第 1 步。创建了一个 NAME() 类型的临时变量,因为我们已使用 NAME()
的默认构造函数将其传递给 std::make_pair
。
第 2 步。使用 std::make_pair
的构造函数之一,它转发(移动)在第一步中创建的临时文件。所以NAME()
的移动构造函数。
第 3 步。最后,由于 foo
的参数是按值传递的,因此在第 2 步中创建的 std::pair
被“移动”(不是“复制”?),进而“移动”@ 987654333@临时最后一次。
案例2:对于foo("an", NAME());
输出
default
move
第 1 步。创建一个临时的NAME
。
第 2 步。这次由于我们没有 std::make_pair
,std::pair
的初始化列表构造函数(如果有)用于“移动”在第 1 步中创建的临时对象。
案例3:对于foo2("an", NAME());
输出
default
move
const copy
我不知道为什么使用复制构造函数而不是移动构造函数,以及为什么使用const
版本而不是非常量版本的复制构造函数。 p>
我的解释是否正确。请纠正我 在哪里我在任何详细的解释步骤中都错了。
【问题讨论】:
非 const 复制构造函数不应该是一个东西。 @sweenish 信不信由你,非常量复制构造函数是 C++ 的东西。类 T 的任何构造函数,只要有一个类型为T &
或 T const &
的强制参数(它还可能有更多的默认参数),都是复制构造函数。
这能回答你的问题吗? Why copy constructor is called in std::vector's initializer list?
有关如何解决此问题的相关问题:Can I list-initialize a vector of move-only type? 和 Can I list-initialize std::vector with perfect forwarding of the elements?
@JaMiT 但是如果 initializer_list 使用复制构造函数,那么为什么在案例 2 的第 2 点中使用移动构造函数?也就是说,在我对案例 2 的解释中,请查看第 2 点。还使用了 std::pair
的 initializer_list 构造函数,然后还应该使用复制构造函数。
【参考方案1】:
std::initializer_list
的元素是const
,不能从中移动。
【讨论】:
这是否意味着我对其他两个调用foo
的解释是正确的?
@AanchalSharma 基本上是的。【参考方案2】:
案例1:对于
foo(std::make_pair("an", NAME()));
输出
default move move
这里的关键问题是了解上述make_pair
调用的类型是什么。它不是我认为你相信的std::pair<std::string, NAME>
,而是std::pair<const char *, NAME>
!
您可以通过检查是否打印 1
来确认这一点。
std::cout << std::is_same_v< decltype(std::make_pair("an", NAME()))
, std::pair<const char *, NAME>> << "\n";
所以,我们观察到:
NAME
是使用默认构造函数构造的。 (输出:default
)
make_pair
移动它以返回其对(输出:move
)
我们不能将该对传递给foo
,而是需要std::pair<std::string, NAME>
。调用隐式pair
转换构造函数,它创建string
并再次移动NAME
。 (输出:move
)
这就是为什么我们观察到两个动作而不是一个动作。
案例2:对于
foo("an", NAME());
输出
default move
这里我们没有调用函数来制作pair,所以"an"
直接用来初始化需要的pair,它拥有std::pair<std::string, NAME>
的权利。我们不再创建具有“错误”类型的对子,因此减少了一步。
关于case 3:我不明白为什么在case 3中调用了move构造函数。我认为为了让vector
移动NAME
就足够了将移动构造函数标记为noexcept
,但即使在这种情况下也会执行复制。我现在不知道。
看起来std::pair
的移动构造函数不是(有条件地)noexcept
,正如人们所期望的那样。这可能是根本原因。
【讨论】:
您能告诉我these 中使用了哪个吗?还是聚合初始化。 @AanchalSharma 我希望我能确定。我的猜测是,在案例 2 中,它是复制列表初始化,调用 (3)。在情况 1 中,我们改为将 (5) 称为隐式。以上是关于程序使用复制构造函数而不是移动构造函数的主要内容,如果未能解决你的问题,请参考以下文章