move语义可以实现资源的所有权转移而非拷贝,是如何实现的?又在何时有用?
实际上这里的资源转移具体分为两步实现:1、move函数改为右值。2、调用右值构造或赋值函数。真正能否实现资源转移取决于第二步。
步骤
move函数改为右值
move函数只是单纯的将一个变量改为右值而已,实际上没有任何其他逻辑。
右值构造/赋值
在某个类中,除了实现了默认的构造/赋值函数,还会实现一个专门用于右值的构造/赋值函数(重载)。这个函数的传入参数就是右值(&&),也就是说前一步的move函数只是为了触发这个右值的重载版本而已。
STL的容器本身支持move,以下就以vector为例。
vector<int>va(10);
cout<<va.begin().base()<<endl;//0x209af8164f0
vector<int>vb(std::move(va));//移动构造
cout<<va.begin().base()<<" "<<vb.begin().base()<<endl;//0 0x209af8164f0
vector<int>vc(vb);//直接构造
cout<<vb.begin().base()<<" "<<vc.begin().base()<<endl;//0x2c4d28164f0 0x2c4d2816520
可以看到如果使用move来构造,va的begin会被直接置0,而vb的begin则变成了原有va的begin。这里实现了原有资源内存不变的情况下所有权由va转移到vb。
而如果不使用move构造(vc),会直接开辟一块新的地址来存储新的vector。
自定义类的move
一个自定义类,c++会内部默认实现一个move,对于类内的支持move的成员变量调用move,其余的则是普通拷贝。
class A{
public:
size_t sz;
vector<int>nums;
explicit A(size_t size){
sz = size;
nums.resize(size);
}
};
int main()
{
A a(10);
A b(std::move(a));
cout<<a.sz<<" "<<a.nums.size()<<endl;
cout<<b.sz<<" "<<b.nums.size()<<endl;
}
/*输出如下
10 0
10 10
*/
可以看到,在类A中定义了一个size_t(不支持move)和一个vector(支持move)。这种情况下如果使用c++自己生成的右值构造函数,会把sz直接拷贝而nums实现了资源转移(a中的nums已经清空了)。
不过也可以自定义右值构造/赋值函数。
class A{
public:
size_t sz;
vector<int>nums;
explicit A(size_t size){
sz = size;
nums.resize(size);
}
A(A&& other)noexcept{
sz=other.sz;
nums=std::move(other.nums);
other.sz=0;
}
};
/*
0 0
10 10
*/
这个A类就自定义实现了一个右值构造覆盖了原本的默认右值构造,可以实现整个类的资源转移(sz和nums的值都被move走了,原本的变量都清空)。
何时有用?
只有在存在资源所有权的变量中才有用。
如果是普通的int、double类型,move实际上就是单纯的拷贝,清空原本数据本身就是一个负担,没必要操作。
如果存在一个指针,move就有意义了,因为move的时候可以在不改变原有地址内容和不开辟新内存的情况下,让另一个指针指向这个资源并清空原本的指针。
就像vector中,本身vector只是持有一块连续内存的begin、size、end等属性,在实际move的时候只需要把这些属性(而非全部内存内容)告诉其他对象即可,其他对象也就相当于持有这部分内存。这时候只需要再清空原本指针就实现了资源转移。
141

被折叠的 条评论
为什么被折叠?



