程序使用 Copy 构造函数而不是 Move 构造函数

Program uses Copy Constructor instead of Move Constructor

提问人: 提问时间:11/13/2021 更新时间:11/14/2021 访问量:415

问:

我正在尝试理解 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() 类型的临时值被占用了,因为我们已使用 的默认构造函数将其传递给。std::make_pairNAME()

第2步。使用其中一个构造函数,它转发(移动)在第 1 步中创建的临时函数。所以的移动构造函数。std::make_pairNAME()

第 3 步。最后,由于参数 to 是通过值传递的,因此在步骤 2 中创建的参数是“移动”(而不是“复制”?),这反过来又最后一次“移动”临时参数。foostd::pairNAME

案例 2:对于foo({"an", NAME()});

输出

default
move

步骤 1。将创建一个临时节点。NAME

第2步。这一次,由于我们没有 ,的初始值设定项列表构造函数(如果有)用于“移动”在步骤 1 中创建的临时。std::make_pairstd::pair

案例 3:对于foo2({{"an", NAME()}});

输出

default
move
const copy

我不知道为什么使用复制构造函数而不是移动构造函数,也不知道为什么使用 const 版本而不是 copy-constructor 的非常量版本

我的解释正确吗?请详细纠正我在任何解释步骤中的错误。

C++ 11 C++17 复制构造函数

评论

3赞 sweenish 11/13/2021
一个非常量复制构造函数不应该是一回事。
0赞 11/13/2021
@sweenish信不信由你,非常量复制构造函数是一件C++的事情。类 T 的任何构造函数,如果具有一个 type 或(也可能具有进一步的默认参数)的必需参数,则都是复制构造函数。T &T const &
0赞 JaMiT 11/13/2021
这回答了你的问题吗?为什么在 std::vector 的初始值设定项列表中调用复制构造函数?
0赞 JaMiT 11/13/2021
对于如何解决这个问题的相关问题:我可以列表初始化仅移动类型的向量吗? 以及 我可以通过完美转发元素来列表初始化 std::vector 吗?
0赞 11/13/2021
@JaMiT 但是,如果initilalizer_list使用复制构造函数,那么为什么在案例 2 的第 2 点中使用移动构造函数呢?也就是说,在我对案例 2 的解释中,请看第 2 点。还有 initializer_list 构造函数,然后还应该使用复制构造函数。std::pair

答:

0赞 yuri kilochek 11/13/2021 #1

的元素 是 ,不能从中移动。std::initializer_listconst

评论

0赞 11/13/2021
这是否意味着我对其他两个调用的解释是正确的?foo
0赞 yuri kilochek 11/13/2021
@AanchalSharma基本上是的。
0赞 chi 11/14/2021 #2

案例一:foo(std::make_pair("an", NAME()));

输出

default
move
move

这里的关键问题是了解上述调用的类型是什么。这不是我认为你所相信的,而是一个!make_pairstd::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";

因此,我们观察到:

  1. NAME使用默认构造函数构造。(输出:default)
  2. make_pair移动它以返回其对(输出:move)
  3. 我们不能将该对传递给 ,而是期望 。调用一个隐式转换构造函数,该构造函数创建并再次移动 。(输出:foostd::pair<std::string, NAME>pairstringNAMEmove)

这就是为什么我们观察到两个动作而不仅仅是一个动作。

案例二:foo({"an", NAME()});

输出

default
move

这里我们不调用函数来制作对,所以是用来直接初始化需要的对,它有 .我们不再创建具有“错误”类型的对,因此少了一步。"an"std::pair<std::string, NAME>

关于案例 3:我不明白为什么在案例 3 中调用移动构造函数。我认为为了允许移动 ,将移动构造函数标记为 就足够了,但即使在这种情况下,也会执行复制。我现在不知道。vectorNAMEnoexcept

看起来移动构造函数并不像人们所期望的那样(有条件地)。这可能是根本原因。std::pairnoexcept

评论

0赞 11/14/2021
你能告诉我其中使用哪一个吗?或者是聚合初始化。
0赞 chi 11/14/2021
@AanchalSharma我希望我能确定。我的猜测是,在情况 2 中,它是 copy-list-initialization,调用 (3)。在情况 1 中,我们改为将 (5) 称为隐式。