调用 std::move 而不使用移动构造函数或移动赋值

Ari*_*Ari 1 c++ move-semantics c++11

我们知道 std::move实际上并没有移动任何东西。它只是将左值引用 (&) 转换为右值引用 (&&)。

那么在下面的例子中,复制构造函数是如何调用的呢?如果没有移动构造函数,那么构造使用 std::move() 的对象如何依靠复制构造函数?变量的绑定究竟是如何b发生的?

struct Test {
  // Default constructor
  Test() {
    std::cout << "Constructor is called." << std::endl;
    mValue = 0;
  }
  
  // Copy constructor
  Test(const Test& rhs) {
    std::cout << "Copy Constructor is called." << std::endl;
    mName = rhs.mName;
    mValue = rhs.mValue;
  }
    
  std::string mName;
  int mValue;
};

int main() {
  Test a;
  Test b = std::move(a);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

Constructor is called.
Copy Constructor is called.
Run Code Online (Sandbox Code Playgroud)

tem*_*def 5

我们来类比一下。考虑一下这段代码:

void doSomething(const int& x) {
    std::cout << "You like " << x << "? That's my favorite number!" << std::endl;
}

int main() {
    doSomething(137); // <-- Here
}
Run Code Online (Sandbox Code Playgroud)

现在,重点关注 中的呼叫main。这段代码编译并运行得很好,但有一些奇怪的地方。请注意,它doSomething接受一个const int&. 这意味着它接受对 的引用int,并且引用(通常)仅绑定到左值。但这里的参数 137 是一个右值。是什么赋予了?

这样做的原因是 C++ 语言特别允许const左值引用绑定到右值,即使常规左值引用不能。例如:

const int& totallyLegal = 137; // Yep, that's fine!
int& whoaNotCoolMan     = 42;  // Compile error!
Run Code Online (Sandbox Code Playgroud)

您可以这样做有几个原因。如果您有const左值引用,则您已承诺可以查看引用的对象,但不能修改它。因此,将左值引用绑定到右值是安全的,因为这样您就无法获取“纯值”并为其分配某些内容。从历史上看,在 C++11 之前,当右值引用不存在时,这使得可以使用左值编写这样的函数:“请以不涉及复制的方式将此参数传递给我const”参考。

现在我们有了左值引用,这条规则引入了一些以前不存在的混乱点。特别是, aconst T&可以绑定到任何类型的表达式的结果T,即使它是 aT&或 a T&&。这就是在您的情况下选择复制构造函数的原因。

不过,这里还有一个细微差别。就像 C++ 编译器会自动为类定义默认构造函数、复制构造函数和赋值运算符(如果您自己不这样做)一样,C++ 编译器也可以自动定义移动构造函数。但是,有一条规则规定,如果类型具有用户定义的复制构造函数,则编译器不会为您生成移动构造函数。因此,您问题的完整答案是“复制构造函数的存在意味着没有定义移动构造函数,并且由于复制构造函数接受const左值引用,因此它将绑定到右值以及左值。”