了解 std::move 和 std::unique_ptr 中的所有权转让 C++

Understanding std::move and Ownership Transfer with std::unique_ptr in C++

提问人:Sami 提问时间:6/26/2023 最后编辑:ProgmanSami 更新时间:6/26/2023 访问量:106

问:

我已经编写了两组函数,F1/test1 和 F2/test2,我正在尝试了解这些函数中 std::move 的行为和 std::unique_ptr 所有权转移的差异。代码如下:

void F1(std::unique_ptr<Dog>&& uPtr)
{
    std::cout << "F1 \n";
}

void test1()
{
    std::unique_ptr<Dog> pD(new Dog("Gunner"));
    F1(std::move(pD));
    if (pD == nullptr)
    {
        std::cout << "Null\n";
    }
    std::cout << "Test \n";
}

void F2(std::unique_ptr<Dog> uPtr)
{
    std::cout << "F2 \n";
}

void test2()
{
    std::unique_ptr<Dog> pD(new Dog("Smokey"));
    F2(std::move(pD));
    if (pD == nullptr)
    {
        std::cout << "Null\n";
    }
    std::cout << "Test \n";
}

在 test1 中,使用 std::move(pD) 作为参数调用 F1,其中 uPtr 是对 std::unique_ptr 的右值引用。调用 F1 后,我检查了 pD 是否为 null。我期望 pD 为 null,因为 std::move(pD) 用于将其传递给 F1,但它不是 null。为什么会这样?

在 test2 中,使用 std::move(pD) 作为参数调用 F2,其中 uPtr 是 std::unique_ptr 类型的按值参数。调用 F2 后,我检查了 pD 是否为 null。在本例中,pD 为 null。正如我所料,因为 std::move(pD) 用于将其传递给 F2,但为什么此行为与 test1 不同?

任何帮助将不胜感激!

C++ unique-ptr 移动语义 rvalue-reference

评论

3赞 HolyBlackCat 6/26/2023
这些东西都是基于惯例的。 是向接收代码提供移动对象的报价,但接收值的人没有义务移动。(例如,如果丢弃 的结果,则无效。作为参数表示您可以移动传递给您的任何内容,但它并不强制您这样做。但是作为参数会强制您复制/移动传递给您的任何内容。std::move(x)std::moveT &&T
0赞 Sami 6/26/2023
谢谢。很好的解释。我们可以说,使用 F1,std::unique_ptr 是通过右值引用传递的,它只是提供另一个名称作为 pD 的别名,而不会更改所有权,因为不涉及移动运算符。
0赞 Jesper Juhl 6/26/2023
std::move只是一个强制转换为右值的引用。有关详细信息,请参阅 en.cppreference.com/w/cpp/utility/move

答:

1赞 Captain Giraffe 6/26/2023 #1

这只是对 r 值参考的转换

F1(std::move(pD));

void F1(std::unique_ptr<Dog>&& uPtr)
{
    std::cout << "F1 \n";
}

实际上对 uPTR 没有任何作用。它可以假设std_ptr即将被销毁,并将其保持在不确定但有效的状态。你的不这样做。

F2正在使用 Move 构造函数,https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr 查看构造函数 5。

2赞 Gilles-Philippe Paillé 6/26/2023 #2

在完成对移动构造函数或移动赋值运算符的调用之前,不会移动对象。 只是从左值到右值的强制转换运算符,此时实际上不会移动任何数据。一个更好的名字是,但这会有点冗长。std::movestd::movestd::cast_to_rvalue

对于 ,指针由右值引用传递,因此函数仍使用来自调用方的实例。由于在函数中未触及,因此原始指针仍将有效。F1uPtr

对于 ,指针是按值传递的,因此在进入函数以创建新对象之前必须调用移动构造函数。即使指针在函数中未触及,移动操作也已发生,因此调用方的实例会受到影响。F2

评论

0赞 Sami 6/26/2023
因此,这意味着两者之间的主要区别在于如何将 std::unique_ptr 传递给函数以及 Dog 实例的所有权会发生什么变化。使用 F1 时,std::unique_ptr 通过右值引用传递,它只是提供另一个名称作为 pD 的别名,而不会更改所有权,因为不涉及移动运算符。相反,F2 按值接受 std::unique_ptr,从而导致 Dog 实例的所有权从 pD 移动到新的 std::unique_ptr (uPtr)。
1赞 Gilles-Philippe Paillé 6/26/2023
@Sam 没错