为什么临时'Test'没有动,只是构造?

Why did not the temporary 'Test' did not moved, but only constructed?

我正在阅读 Scott Meyers 的 Effective Modern C++ 这本书,作者在其中提到

"Copies of rvalues are generally move constructed, while copies of lvalues are usually copy constructed."

所以我想通过编写一个 Test class 来测试它,它将临时构建到一个 dummy 函数,该函数将右值引用作为 [= 的参数16=] class.

我期待看到上面引用的输出,如:

Constructed...
Moving...
Hello world

但我只有:

Constructed...
Hello world

为什么移动构造函数没有调用?像 dummy(Test{}); 这样临时传递给 dummy 是否有任何未定义的行为?我正在使用 C++17.

代码如下:

#include <iostream>

class Test
{
public:
    Test()                       { std::cout << "Constructed...\n"; }
    Test(const Test&)            { std::cout << "Coping...\n"; }
    Test& operator=(const Test&) { std::cout << "Copy=...\n"; return *this; }
    Test(Test&&)                 { std::cout << "Moving...\n"; }
    Test& operator=(Test&&)      { std::cout << "Move=...\n"; return *this; }
    void print() const
    {
        std::cout << "Hello world\n";
    }
};

void dummy(Test&& test)
{
    test.print();  // is the behavior is good?
}

int main()
{
    dummy(Test{});
    return 0;
}

基本上,只构造了一个 Test 实例:dummy(Test{}) 但是如果要从一个实例移动到另一个实例,则此移动意味着至少存在两个实例。

出于同样的原因,以下代码不会调用复制构造函数:

class Test {
   ...
};

void dummy(Test& t) { // passed as an lvalue reference
   test.print();
}

int main() {
   Test t;
   dummy(t);
   return 0;
}

...因为对象是通过引用传递的。在上面的代码中,它是一个左值引用,而在你给出的例子中它是一个右值引用,但仍然是一个引用。

如果你想按值传递相反,你需要在你的函数参数类型中反映它:

void dummy(Test t) {
  ...
}

然后需要调用构造函数以按值传递t 除非发生复制省略,请参见下面的添加

请注意 std::move 实际上并没有移动任何东西。它将输入转换为右值引用并 returns 它。


虽然有一点细微差别。 C++17 保证不会为纯右值赋值创建临时对象,即 dummy(Test{}) 在这种情况下不会调用复制或移动构造函数,因为创建临时对象被省略了。尽管语言不需要,但复制省略可能仍会在 C++17 之前的版本中发生。因此,如果您想观察在将 prvalue Test{} 按值传递给 dummy 函数时调用移动构造函数(如上例所示),请确保设置 -fno-elide-constructors 编译器标志。