【问题标题】:move constructor and copy constructor in C++C++中的移动构造函数和复制构造函数
【发布时间】:2018-05-16 16:04:45
【问题描述】:

我的理解是,当我们从函数返回本地对象时,如果存在移动构造函数,则会调用它。但是,我遇到了调用复制构造函数的情况,如下面的函数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对象;为什么没有调用移动构造函数?

【问题讨论】:

  • 你能缩短你的例子吗?我不敢相信所有这些行都是相关的。
  • 我很好奇,为什么要使用-fno-elide-constructors 构建?
  • return *up 必须复制 - *up 不可移动。
  • 您的程序引入了未定义的行为。您不会在声明为返回引用的函数中返回引用。 tNode&amp; operator=(const tNode&amp; node) 之类的函数——因此无法从您的代码中推测任何内容。
  • @JesperJuhl:大概是为了演示被问及的语言行为?

标签: c++ copy-constructor move-constructor


【解决方案1】:
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

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

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

行为不同,因为您没有返回函数本地对象。 *up 给你一个 tNode&amp; 所以 return 语句不能把它当作一个右值。由于它是左值,因此您必须调用复制构造函数将其复制到返回值中。然后,和第一个例子一样,调用移动构造函数将对象从返回值移动到n2

【讨论】:

    【解决方案2】:

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

    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);
    }
    

    【讨论】:

    • 我只想指出return std::move(...) 通常是一种反模式,因为它可能会阻止返回值优化。在这个例子中,它只是“正确”的,因为底层的例子是没有意义的。
    猜你喜欢
    • 1970-01-01
    • 2018-03-12
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 2018-09-23
    • 2022-11-21
    • 2013-10-13
    • 2020-05-14
    相关资源
    最近更新 更多