»
(09)C++14 任意值的移动操作move
(18)C++14 智能指针及引用计数*
(19)C++14 多线程pthread*
(20)C++14 同步锁mutex*
(24)C++中的计时与等待*
(25)C++中的高精度计算*
(27)C++14 std::string*
(31)C++高级技能*
这里讲的任意值的移动,指的是任意左值或者任意右值都可以移动。这里详细地讲解C++中的=std::move()移动操作。
为什么需要move操作?
先看非move的例子:
#include <iostream>
#include <vector>
class Car{
private:
std::string name;
std::vector<std::string> partNames;
bool hasInitialized=false;
public:
Car(std::string name,std::vector<std::string> partNames){
this->name=name;
this->partNames=partNames;
std::cout<<"Car()init name:"<<name<<" partNames:";
for(auto& partName:partNames){
std::cout<<partName<<" ";
}
std::cout<<std::endl;
}
Car(const Car& c){
std::cout<<"Car(Car&c)called"<<std::endl;
this->name=c.name+"AfterCopiedOnce";
this->partNames=c.partNames;
}
~Car(){
std::cout<<"~Car() name:"<<name<<" partNames:";
for(auto& partName:partNames){
std::cout<<partName<<" ";
}
std::cout<<std::endl;
}
Car& operator=(const Car& c){
std::cout<<"Car& operator=(const Car& c)called"<<std::endl;
this->name=c.name;
this->partNames=c.partNames;
return *this;
}
};
Car returnCarFromCar(Car originalC){
std::cout<<"in returnCarFromCar(Car c)"<<std::endl;
std::cout<<"begin return"<<std::endl;
return originalC;
}
int main( )
{
std::vector<std::string>partNames{"part1","part2"};
Car c("1",partNames);
returnCarFromCar(c);
std::cout<<"after returnCarFromCar(Car c) called"<<std::endl;
std::cout<<std::endl;
std::cout<<"begin Car cb=returnCarFromCar(Car c) called"<<std::endl;
Car cb = returnCarFromCar(c);
std::cout<<"after Car cb=returnCarFromCar(Car c) called"<<std::endl;
std::cout<<std::endl;
std::cout<<"begin cc=returnCarFromCar(Car c) called"<<std::endl;
Car cc("2",{});
cc = returnCarFromCar(c);
std::cout<<"after cc=returnCarFromCar(Car c) called"<<std::endl;
}
程序的打印结果为:
Car()init name:1 partNames:part1 part2 //对应主函数中的Car c;语句;初始化Car操作
Car(Car&c)called //对应主函数中的returnCarFromCar函数的参数传入过程;传递给returnCarFromCar函数的参数Car c时进行的拷贝构造操作
in returnCarFromCar(Car c) //对应returnCarFromCar函数体内;
begin return //对应returnCarFromCar函数体内开始从函数体返回;
Car(Car&c)called //通过拷贝构造函数拷贝变量originalC的值,构造函数体的右值空间
~Car() name:1AfterCopiedOnceAfterCopiedOnce partNames:part1 part2 //右值空间销毁(因为没有新的左值Label来保持右值,所以,右值直接销毁)
~Car() name:1AfterCopiedOnce partNames:part1 part2 //函数体的参数orignialC对象销毁
after returnCarFromCar(Car c) called //进入主函数
begin Car cb=returnCarFromCar(Car c) called //开始第二次函数调用
Car(Car&c)called //参数值传入函数的originalC变量进行的拷贝
in returnCarFromCar(Car c)
begin return //函数开始返回
Car(Car&c)called //通过拷贝构造函数拷贝变量originalC的值,构造函数体的右值空间 //第二步赋值操作:因为cb为新建Label,还没有分配空间,所以,函数体直接将cb的地址改到函数体的右值空间并保持住右值空间
~Car() name:1AfterCopiedOnce partNames:part1 part2 //参数originalC变量的析构
after Car cb=returnCarFromCar(Car c) called
begin cc=returnCarFromCar(Car c) called
Car()init name:2 partNames:
Car(Car&c)called
in returnCarFromCar(Car c) //进入函数体
begin return //函数体返回
Car(Car&c)called //拷贝构造函数体右值
Car& operator=(const Car& c)called //赋值操作符重载(因为cc为旧label,数据空间已经存在,有可能还有别的指针指向着label所指向的这块数据空间,所以,不能直接修改旧label的地址指向,只能也必须调用旧label对象的赋值操作符重载将数据加载到这块数据空间)
~Car() name:1AfterCopiedOnceAfterCopiedOnce partNames:part1 part2 //析构函数体右值
~Car() name:1AfterCopiedOnce partNames:part1 part2 //析构函数体参数originalC
after cc=returnCarFromCar(Car c) called //退出函数体
~Car() name:1AfterCopiedOnceAfterCopiedOnce partNames:part1 part2
~Car() name:1AfterCopiedOnceAfterCopiedOnce partNames:part1 part2
~Car() name:1 partNames:part1 part2
从以上代码看出:函数体返回一个对象的过程中,做了:1、函数体内变量拷贝构造到函数返回值空间;2、如果Label为新Label,则修改Label、指向函数返回值空间;3、如果Label为旧Label,则调用Label变量的赋值操作符重载拷贝函数返回值空间。做了1~2次数据对象的深度拷贝。
而这种深度拷贝是可以被优化的移动操作。
移动操作分为:移动构造和移动赋值。
三种被移动的情况:(1)函数返回值对象移动数据到右值空间时;(2)函数的右值空间的数据被赋值给一个已经存在的左值Label时;(3)一个左值与一个对象显示地调用左值=std::move(对象)时。
C++中函数的返回值空间就是右值空间。函数的返回必定新建右值空间。新建右值空间后必定调用一次返回值对象的移动构造方法,此调用等价于:(1)函数返回前函数体自动实现:右值空间=std::move(返回对象)。
函数的右值空间赋值给左值时,当由新建左值接收时,会直接修改新建左值的地址指向右值空间而不做任何其他动作,此过程等价于:新左值直接指向右值空间;当由旧左值接收时,会再调用一次旧左值的移动赋值函数,此调用等价于:(2)函数返回前函数体自动实现:旧左值=std::move(右值空间对象),将右值空间里的成员数据移动赋值到旧左值的成员变量上。
注意:左值使用=std::move(argv)函数进行显式调用赋值时,不会新建任何右值空间,而是只进行对象成员的移动操作,也就是:(3)左值变量显式调用=std::move(参数),程序只会调用该左值对象的移动赋值函数进行参数对象的成员的移动操作(如果这个左值是新建的左值,则会先调用默认构造方法构造此左值,再做移动赋值操作)。
C++11引入赋值移动操作解决的问题:解决函数返回过程中的值对象深度拷贝多次导致的速度缓慢的问题。
移动构造函数的定义方式:
类名(类名&&被移动变量) noexcept{}
比如:
Car(Car&& other) noexcept{}
主要功能是对被移动对象other的成员变量做std::move操作,从而加速新的对象的构造过程。
移动赋值函数的定义方式:
类名& operator=(类名&&被移动变量) noexcept{}
比如:
Car& operator=(Car&& c) noexcept{}
主要功能是对被移动对象other的内存变量做std::move操作,从而加速新的对象的构造过程。
例子代码:
class Car{
private:
std::string name;
std::vector<std::string> partNames;
public:
Car(const std::string name,const std::vector<std::string> partNames){
this->name=name;
this->partNames=partNames;
std::cout<<"Car()init name:"<<name<<" partNames:";
for(auto& partName:partNames){
std::cout<<partName<<" ";
}
std::cout<<std::endl;
}
Car(const Car& c){
std::cout<<"Car(Car&c)called"<<std::endl;
this->name=c.name+"AfterCopiedOnce";
this->partNames=c.partNames;
}
Car(Car&& c) noexcept{
std::cout<<"Car(Car&&c) move constructor called while being moved"<<std::endl;
this->name=std::move(c.name);
this->partNames=std::move(c.partNames);
}
Car& operator=(Car&& c) noexcept{
std::cout<<"Car& operator=(Car&& c) move assignment function called while being moved"<<std::endl;
this->name=std::move(c.name);
this->partNames=std::move(c.partNames);
}
~Car(){
std::cout<<"~Car()"<<(this->name.compare("")==0?"(empty name)":this->name)<<" is being destroyed";
std::cout<<" partNames:";
for(auto& partName:partNames){
std::cout<<partName<<" ";
}
std::cout<<std::endl;
}
void addPartName(std::string partName){
this->partNames.push_back(partName);
}
void print(){
std::cout<<this->name<<":";
for(auto& partName:this->partNames){
std::cout<<partName<<" ";
}
std::cout<<endl<<endl;
}
};
Car addPartName(Car c,std::string partName){
c.addPartName(partName);
return c;
}
int main( )
{
std::vector<std::string>partNames{"part1","part2"};
Car *c=new Car("1",partNames);
Car cb("2",partNames);
Car cc("3",partNames);
Car cd = std::move(*c);
delete c;
c=new Car("4",partNames);
cd = std::move(*c);
delete c;
std::cout<<endl<<endl;
Car de=addPartName(cc,"part3");//此处调用的是移动构造+修改de Label的地址指向
de.print();
de=addPartName(cc,"part3");//此处调用的是移动构造+移动赋值
de.print();
}
打印结果:
Car()init name:1 partNames:part1 part2
Car()init name:2 partNames:part1 part2
Car()init name:3 partNames:part1 part2
Car(Car&&c) move constructor called while being moved
~Car()(empty name) is being destroyed partNames:
Car()init name:4 partNames:part1 part2
Car& operator=(Car&& c) move assignment function called while being moved
~Car()(empty name) is being destroyed partNames:
Car(Car&c)called
Car(Car&&c) move constructor called while being moved//移动到函数返回值空间,也就是右值空间(因为是在函数体的返回阶段,所以,程序自动调用了移动构造操作)
~Car()(empty name) is being destroyed partNames://释放函数体的参数对象cc
3AfterCopiedOnce:part1 part2 part3 //函数体返回,函数的右值空间已经被左值Label de保持住,不会被释放。
Car(Car&c)called
Car(Car&&c) move constructor called while being moved//移动到右值空间(因为是在函数体的返回阶段,所以,程序自动调用了移动构造操作)
Car& operator=(Car&& c) move assignment function called while being moved//左值Label de=函数返回值空间(右值)的赋值操作。(因为是在函数体的返回阶段,所以,程序自动调用了移动赋值操作)
~Car()(empty name) is being destroyed partNames://右值空间释放
~Car()(empty name) is being destroyed partNames://函数体的参数对象cc的释放
3AfterCopiedOnce:part1 part2 part3 //函数体返回,函数的右值空间已经被释放
~Car()3AfterCopiedOnce is being destroyed partNames:part1 part2 part3
~Car()4 is being destroyed partNames:part1 part2
~Car()3 is being destroyed partNames:part1 part2
~Car()2 is being destroyed partNames:part1 part2
总结:
左值=std::move(参数)不会做任何新建右值的操作,只会做移动赋值操作。