在没有移动构造函数或移动赋值的情况下调用 std::move
Calling std::move without move constructor or move assignment
我们知道 std::move does not actually move anything。它只是将左值引用 (&) 转换为右值引用 (&&)。
那么在下面的例子中,拷贝构造函数是怎么调用的呢?如果没有移动构造函数,构造一个使用 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;
}
输出:
Constructor is called.
Copy Constructor is called.
类比一下。想想这段代码:
void doSomething(const int& x) {
std::cout << "You like " << x << "? That's my favorite number!" << std::endl;
}
int main() {
doSomething(137); // <-- Here
}
现在,关注 main
中的通话。这段代码编译和运行都很好,但是有一点奇怪。请注意 doSomething
接受了 const int&
。这意味着它接收 reference 到 int
,并且引用(通常)仅绑定到左值。但是这里的参数 137 是一个右值。给出了什么?
这样做的原因是 C++ 语言特别允许 const
左值引用绑定到右值,即使常规左值引用不能。例如:
const int& totallyLegal = 137; // Yep, that's fine!
int& whoaNotCoolMan = 42; // Compile error!
您可以这样做有几个原因。如果你有一个 const
左值引用,你已经承诺你可以 查看 引用的对象,但你不能 修改 它。因此,将左值引用绑定到右值是安全的,因为这样您就没有办法获取“纯值”并为其赋值。从历史上看,pre-C++11,当右值引用不存在时,这使得编写函数可以说“请以不涉及制作副本的方式将此参数传递给我”通过使用const
左值引用。
现在我们有了左值引用,这条规则引入了一些以前没有的混淆点。特别是,const T&
可以绑定到 T
类型的任何表达式的结果,即使它是 T&
或 T&&
。这就是在您的案例中选择复制构造函数的原因。
不过这里还有一个细微差别。与 C++ 编译器自动为 class 定义默认构造函数、复制构造函数和赋值运算符的方式相同,前提是您自己不这样做,C++ 编译器也可以自动定义移动构造函数。然而,有一条规则说,如果一个类型有一个 user-defined 复制构造函数,那么编译器将不会为你生成一个移动构造函数。所以你的问题的完整答案是“你的复制构造函数的存在意味着没有定义移动构造函数,并且由于复制构造函数采用 const
左值引用,它将绑定到右值和左值。”
我们知道 std::move does not actually move anything。它只是将左值引用 (&) 转换为右值引用 (&&)。
那么在下面的例子中,拷贝构造函数是怎么调用的呢?如果没有移动构造函数,构造一个使用 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;
}
输出:
Constructor is called.
Copy Constructor is called.
类比一下。想想这段代码:
void doSomething(const int& x) {
std::cout << "You like " << x << "? That's my favorite number!" << std::endl;
}
int main() {
doSomething(137); // <-- Here
}
现在,关注 main
中的通话。这段代码编译和运行都很好,但是有一点奇怪。请注意 doSomething
接受了 const int&
。这意味着它接收 reference 到 int
,并且引用(通常)仅绑定到左值。但是这里的参数 137 是一个右值。给出了什么?
这样做的原因是 C++ 语言特别允许 const
左值引用绑定到右值,即使常规左值引用不能。例如:
const int& totallyLegal = 137; // Yep, that's fine!
int& whoaNotCoolMan = 42; // Compile error!
您可以这样做有几个原因。如果你有一个 const
左值引用,你已经承诺你可以 查看 引用的对象,但你不能 修改 它。因此,将左值引用绑定到右值是安全的,因为这样您就没有办法获取“纯值”并为其赋值。从历史上看,pre-C++11,当右值引用不存在时,这使得编写函数可以说“请以不涉及制作副本的方式将此参数传递给我”通过使用const
左值引用。
现在我们有了左值引用,这条规则引入了一些以前没有的混淆点。特别是,const T&
可以绑定到 T
类型的任何表达式的结果,即使它是 T&
或 T&&
。这就是在您的案例中选择复制构造函数的原因。
不过这里还有一个细微差别。与 C++ 编译器自动为 class 定义默认构造函数、复制构造函数和赋值运算符的方式相同,前提是您自己不这样做,C++ 编译器也可以自动定义移动构造函数。然而,有一条规则说,如果一个类型有一个 user-defined 复制构造函数,那么编译器将不会为你生成一个移动构造函数。所以你的问题的完整答案是“你的复制构造函数的存在意味着没有定义移动构造函数,并且由于复制构造函数采用 const
左值引用,它将绑定到右值和左值。”