对象移动的特点
左值引用(&)
int i = 42;int &r = i; //正确,r引用iint &&rr = i; //错误,不能将一个右值引用到左值上int &r2 = i * 42; //错误,i*42是一个右值const int &r3 = i * 42;//正确,我们可以将一个const的引用绑定到一个右值上int &&rr2 = i * 42; //正确,将rr2绑定到乘法结果上(右值)
int ret(int i) {return i * 2;}int& ret2(int& i) {return i;}
int &r = ret(1); //错误int &&rr = ret(1); //正确
int &r2 = ret2(1); //正确int &&rr2 = ret2(1); //错误
左值持久、右值短暂
int &&rr1 = 42; //正确,42是字面值int &&rr2 = rr1; //错误,表达式rr1是左值
int &&rr1 = 42; //正确,42是字面值int &&rr2 = std::move(rr1); //正确了
class StrVec{public:StrVec():elements(nullptr), first_free(nullptr), cap(nullptr) {}~StrVec(){if (elements) { //如果数组不为空//释放内存}}private:std::string *elements; //指向数组首元素的指针std::string *first_free;//指向数组第一个空闲元素的指针std::string *cap; //指向数组尾后位置的指针};
StrVec(StrVec &&s) noexcept:elements(s.elements),first_free(s.first_free),cap(s.cap){s.elements = s.first_free = s.cap = nullptr;}
StrVec& operator=(StrVec &&rhs){//检测自我赋值,不能写成*this != sif (this != &rhs) {if (this->elements) {//释放自身的资源}//开始接管参数的资源elements = rhs.elements;first_free = rhs.first_free;cap = rhs.cap;
//将参数置为空rhs.elements = rhs.first_free = rhs.cap = nullptr;}return *this;}
//编译器会为X和hasX合成移动操作
struct X {
int i; //内置类型可以移动
std::string s;//string定义了自己的移动操作
};
struct hasX {
X mem; //X有合成的移动操作
};
int main()
{
X x;
X x2 = std::move(x); //使用合成的移动构造函数
hasX hx;
hasX hx2 = std::move(hx); //使用合成的移动构造函数
return 0;
}
//假设Y是一个类,且Y定义了自己的拷贝构造函数但未定义自己的移动构造函数
struct hasY {
hasY() = default;
hasY(hasY &&) = default;
Y mem;
};
int main()
{
hasY hy;
hasY hy2 = std::move(hy); //错误,移动构造函数是删除的
return 0;
}
//StrVec只定义了移动构造函数与移动赋值运算符,但是没有定义拷贝构造函数与拷贝赋值运算符
class StrVec
{
//...
public:
StrVec(StrVec &&s)noexcept{}
StrVec& operator=(StrVec &&rhs){}
//...
};
int main()
{
StrVec v1, v2;
v1 = v2; //错误,SreVec的拷贝赋值运算符被定义为删除的
return 0;
}
演示案例
//假设SreVec的拷贝构造函数/拷贝赋值运算符,移动构造函数/移动拷贝赋值运算符都定义了
class StrVec{};
StrVec getVec(istream &)
{
//该函数返回一个SreVec对象(右值)
}
int main()
{
StrVec v1, v2;
v1 = v2; //v2是个左值,此处调用拷贝赋值运算符
v2 = getVec(cin); //getVec函数返回一个右值,此处调用移动赋值运算符
return 0;
}
class Foo {
public:
Foo() = default;
Foo(const Foo&); //拷贝构造函数
//未定义移动构造函数
};
int main()
{
Foo x;
Foo y(x); //调用拷贝构造函数
Foo z(std::move(x));//因为Foo没有定义移动构造函数,所以此处调用的是拷贝构造函数
return 0;
}
class HasPtr {
public:
HasPtr(const std::string &s = std::string())
:ps(new std::string(s)), i(0) {}
HasPtr(const HasPtr& p) //拷贝构造函数
:ps(new std::string(*(p.ps))), i(p.i) {}
~HasPtr() { delete ps; }
friend void swap(HasPtr&, HasPtr&);
private:
std::string *ps;
int i;
};
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, lhs.ps);//交换指针,而不是string数据
swap(lhs.i, lhs.i); //交换int成员
}
class HasPtr {
public:
//其他内容同上
//移动构造函数
HasPtr(HasPtr &&p)noexcept :ps(p.ps), i(p.i) { p.ps = 0; }
//这个赋值运算符即是移动赋值运算符,也是拷贝赋值运算符
HasPtr& operator=(HasPtr rhs)
{
swap(*this,rhs);
return *this;
}
};
HasPtr hp;HasPtr hp2;
//hp2是一个左值。所以先调用拷贝构造函数复制一份HasPtr对象给operator=参数//再调用operator=函数将hp2赋值给hphp = hp2;
//此处hp2显式成为一个右值。所以先调用移动构造函数构造一份HasPtr对象给operator=参数//再调用operator=函数将hp2赋值给hphp = std::move(hp2);
void push_(const X&); //拷贝版本void push_(X&&); //移动版本
class StrVec{public://其他同上void push_back(const std::string&);//拷贝元素void push_back(std::string&&); //移动元素private:static std::allocator<std::string> alloc; //分配元素//其他同上};
void StrVec::push_back(const std::string& s){chk_n_alloc(); //自定义函数,用来检测是否空间足够
//在first_free指向的元素中构造s的一个副本,此处construct会调用string的构造函数来构造新元素alloc.construct(first_free++, s);}
void StrVec::push_back(std::string&&){chk_n_alloc();
//此处由于参数为std::move()类型,因此construct会调用string的移动构造函数来构造新元素alloc.construct(first_free++,std::move(s));}
StrVec vec;string s = "some string or another";
vec.push_back(s); //s为左值,因为调用push_back(const string&)vec.push_back("done"); //调用push_back(string&&)
string s1 = "a value", s2 = "another";
//s1+s2是一个右值auto n = (s1 + s2).find('a');
string s1 = "a value", s2 = "another";s1 + s2 = "wow"; //s1+s2是一个右值,我们此处对一个右值进行了赋值(无意义)
class Foo {public:Foo someMem()&const; //错误,const必须在&&前面Foo anotherMem()const&;//正确};
class Foo {public://此参数后面有一个&,因此这个函数只能被一个左值对象调用Foo &operator=(const Foo&)&;};
Foo &Foo::operator=(const Foo& rhs)&{//执行将rhs赋予本对象的操作(代码省略)
return *this;}
Foo& retFoo(){ //一个函数,返回Foo类,返回左值(引用)}
Foo retVal(){ //一个函数,返回Foo类,返回右值}
int main(){Foo i, j;i = j;
retFoo() = j; //正确,retFoo()返回一个左值retVal() = j; //错误,retVal()返回一个右值
i = retFoo(); //正确,我们可以将一个左值作为赋值操作的右侧运算对象i = retVal(); //正确,我们可以将一个右值作为赋值操作的右侧运算对象return 0;}
class Foo {public:void push_back() && {} //这个函数只能被一个右值Foo对象调用};
Foo& retFoo(){//一个函数,返回Foo类,返回左值(引用)}
Foo retVal(){//一个函数,返回Foo类,返回右值}
int main(){Foo i;
i.push_back(); //错误,i是一个左值retFoo().push_back(); //错误retVal().push_back(); //错误(这个没搞懂,retVal应该返回右值的啊,但是编译器报错)std::move(retVal()).push_back();//正确
return 0;}
class Foo {public://此函数只可以用于右值Foo sorted() && {std::sort(data.begin(), data.end());return *this;}//此函数可以用于左值或const类型的右值(因为其带有const,见下面的重载介绍)Foo sorted()const & {Foo ret(*this); //拷贝一个副本std::sort(ret.data.begin(), ret.data.end()); //排序副本return ret; //返回结果}private:std::vector<int> data;};
Foo& retFoo(){//一个函数,返回Foo类,返回左值(引用)}
Foo retVal(){//一个函数,返回Foo类,返回右值}
const Foo retVal2(){ //一个函数,返回Foo类,返回右值,且为const}
int main(){retFoo().sorted(); //调用sorted()const&retVal().sorted(); //调用sorted() &&retVal2().sorted();//调用sorted()const&return 0;}
class Foo {
public:
//下面两者形成重载
Foo sorted();
Foo sorted()const;
};
class Foo {public:Foo sorted()&&;Foo sorted()const&&; //正确,与上面形成重载//Foo sorted()const; 这个是错误的};