【问题标题】: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_pairstd::pair 的初始化列表构造函数(如果有)用于“移动”在第 1 步中创建的临时对象。

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

输出

default
move
const copy

我不知道为什么使用复制构造函数而不是移动构造函数,以及为什么使用const 版本而不是非常量版本的复制构造函数。 p>

我的解释是否正确。请纠正我 在哪里我在任何详细的解释步骤中都错了。

【问题讨论】:

标签: c++ c++11 constructor c++17 copy-constructor


【解决方案1】:

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

【讨论】:

  • 这是否意味着我对其他两个调用foo 的解释是正确的?
  • @AanchalSharma 基本上是的。
【解决方案2】:

案例1:对于foo(std::make_pair("an", NAME()));

输出

default
move
move

这里的关键问题是了解上述make_pair 调用的类型是什么。它不是我认为你相信的std::pair&lt;std::string, NAME&gt;,而是std::pair&lt;const char *, NAME&gt;

您可以通过检查是否打印 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. 我们不能将该对传递给foo,而是需要std::pair&lt;std::string, NAME&gt;。调用隐式pair 转换构造函数,它创建string 并再次移动NAME。 (输出:move

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

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

输出

default
move

这里我们没有调用函数来制作pair,所以"an"直接用来初始化需要的pair,它拥有std::pair&lt;std::string, NAME&gt;的权利。我们不再创建具有“错误”类型的对子,因此减少了一步。

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

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

【讨论】:

  • 您能告诉我these 中使用了哪个吗?还是聚合初始化。
  • @AanchalSharma 我希望我能确定。我的猜测是,在案例 2 中,它是复制列表初始化,调用 (3)。在情况 1 中,我们改为将 (5) 称为隐式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-21
  • 1970-01-01
  • 2015-06-10
  • 1970-01-01
  • 2022-01-12
  • 2020-08-13
相关资源
最近更新 更多