摘要:右值引用不会拷贝/赋值。在拷贝初始化或赋值时,调用拷贝构造函数/运算符还是移动构造函数/运算符都是允许的,实际调用拷贝的还是移动的,要看类是否定义了移动构造函数/运算符,若定义了,则调用移动的;否则调用拷贝的。
有一个类HasPtr,假设已定义了一个HasPtr 对象 hp。文末给出了该类的定义。
对于 HasPtr&& hp1 = std::move(hp);,这个是不会进行任何拷贝/赋值的,也就不会调用任何构造函数或赋值运算符,因为这个就是给 hp 添加了一个别名,执行该语句后 hp 仍是原来的值,地址也不变。
// main.cpp
#include "HasPtr_draft.h"
int main()
{
HasPtr hp("hello");
cout << *hp.ps << "\t" << &hp << "\n";
HasPtr&& hp1 = std::move(hp);
cout << *hp1.ps << "\t" << &hp1 << "\n";
return 0;
}
输出:
hello 0x808f3ff6b0
hello 0x808f3ff6b0
对于 HasPtr hp1 = std::move(hp);,这个可能会调用移动构造函数,但也有可能调用拷贝构造函数。首先我们知道 hp1 是新定义的一个对象,所以这个语句肯定会执行拷贝初始化。赋值号右边,std::move(hp) 返回一个右值。
int main()
{
HasPtr hp("hello");
cout << hp.ps << "\n";
HasPtr hp1 = std::move(hp);
cout << hp.ps << "\n";
return 0;
}
若未定义移动构造函数,则调用拷贝构造函数,该右值会传给拷贝构造函数的形参,此时 HasPtr&& 转换为 const HasPtr&,这是允许的。
输出:
0x180215f4b60
move constructor
0
若定义了移动构造函数,则调用移动构造函数。此时,虽然拷贝构造函数与移动构造函数都是参数匹配的,但移动构造函数是精确匹配,无需转换,因此会被调用。
输出:
0x19ca3f34b60
copy constructor
0x19ca3f34b60
对于 HasPtr hp1; hp1 = std::move(hp);,这个是拷贝赋值,也是要看类是否定义了移动赋值运算符。
int main()
{
HasPtr hp("hello");
cout << hp.ps << "\n";
HasPtr hp1;
hp1 = std::move(hp);
cout << hp.ps << "\n";
return 0;
}
若未定义移动赋值运算符,则调用拷贝赋值运算符。
输出:
0x19e78c74b60
copy-assignment
0x19e78c74b60
若定义了移动赋值运算符,则调用移动赋值运算符,因其精确匹配。
输出:
0x2b0383e4b60
move-assignment
0
// HasPtr_draft.h
#ifndef HASPTR_DRAFT_H
#define HASPTR_DRAFT_H
#include
#include
using std::cout;
using std::string;
struct HasPtr
{
int i;
string *ps;
HasPtr(const string &s = "") : ps(new string(s)), i(0) {}
HasPtr(const HasPtr &hp) : i(hp.i), ps(new string(*hp.ps)) // 拷贝构造函数
{ cout << "copy constructor\n"; }
HasPtr &operator=(const HasPtr &rhs) // 拷贝赋值运算符
{
auto p = new string(*rhs.ps);
delete ps;
ps = p;
i = rhs.i;
cout << "copy-assignment\n";
return *this;
}
HasPtr(HasPtr &&hp) noexcept : i(hp.i), ps(hp.ps) // 移动构造函数
{
hp.ps = 0;
cout << "move constructor\n";
}
HasPtr &operator=(HasPtr &&hp) noexcept // 移动赋值运算符
{
i = hp.i;
ps = hp.ps;
hp.ps = 0;
cout << "move-assignment\n";
return *this;
}
};
#endif