我们可能知道函数里的临时对象的生存周期在函数内,也可能更详细的知道临时对象离开它的作用域就会消亡,不过我们平时可能很少直观感受到这一点。因此我们想象下面一个场景,如果我们想要定义一个int*类型返回值的函数,该函数构建一个int类型的值。此时我们一般采用如下方式写:
int* func() {
return new int(1);
}
这样我们就可以得到一个动态内存,因此我们需要在调用完返回值后delete释放内存(当然可以使用智能指针)。不过很多时候我们不喜欢使用动态内存,然后我们想到了在函数里构建一个int变量并返回其地址,如下:
int* func(bool flag) {
int a=1;
return &a;
}
int main() {
int* p=func(true);
if(p==nullptr) {
std::cout<<"nullptr";
}
return 0;
}
这样看起来不错,但是当我们运行起来会发现返回的p是nullptr,而不是1。结合临时对象我们此时可能明白了,a在func结束之后就会被回收。那么此时我们好像只剩下两个选择,要么返回一个new的对象,要么就在函数外构建变量然后函数内赋值。此时我们可能会想如果能够延长a的生存期到p的生存期结束就好了,而右值引用可以延长生存期。
我们先看看右值引用的基本用法
int a=2;
int&& b=std::move(a);
int *p=b;
std::cout<
第二行的std::move使得右值引用可以指向左值。我们发现左值引用无法指向右值,但是右值引用通过std::move可以指向左值。右值引用可以指向右值,也可以被指针取地址。我们再用如下方式实现上述问题:
int&& get() {
int a=1;
return std::move(a);
}
int* getPtr() {
int&& a = get();
return &a;
}
int *p=getPtr();
此时p已经指向int型的1了,这样我们也直观验证了右值引用可以延长对象生存周期的特点。
2.移动构造函数:上文简单介绍了右值引用的一些特点,但是右值引用最大的用处(可能没有之一)在于构建移动构造函数来减少拷贝次数。
2.1 深拷贝:简单的说如果类里面有指针,那么浅拷贝就是只拷贝指针,拷贝者和被拷贝者指向同一个地址。深拷贝就是构造一个等大的内存,然后从被拷贝者指针指向的内存复制数据到自己中。如下伪代码:
class node {
public:
int size;
int* ptr;
node() :size(0), ptr(nullptr) {};
node(const node& otherNode) {
size = otherNode.size;
ptr = new int[size];
for (int i = 0; i < size; i++) {
ptr[i] = otherNode.ptr[i];
}
}
node& operator=(const node& otherNode) {
size = otherNode.size;
ptr = new int[size];
for (int i = 0; i < size; i++) {
ptr[i] = otherNode.ptr[i];
}
}
};
我们需要拷贝ptr指向的所有内容,但是很多时候我们赋值之后就不需要nweNode了,因此我们可能会想如果能直接接管newNode的内容就好了。但是我们不能直接让newNode.ptr=nullptr,因为不是所有的赋值 *** 作后newNode就不需要了,因此我们需要使用新的函数进行移动构造。
2.2 尝试使用左值引用来进行移动构造:我们第一反应就是构建如下拷贝构造函数:
node(node& otherNode);
然而我们传递的是左值,这意味着我们不能使用下面方式进行构造:
node a(node());
因为左值引用不能接收右值,而形如node()这类函数显然是右值。因此我们想到const引用可以指向右值,因此想到下面形式:
node(const node& otherNode); //与深拷贝重定义,想到下列方式
node(const node& otherNode, char arg);
我们又得到一个新的方式,但是首先我们需要传递一个无用参数arg,它仅仅用来区分深拷贝。此外,当我们实现时会发现由于是const引用,因此我们不能修改otherNode,进而就不能写以下语句:
otherNode.ptr=nullptr;
这样左值引用方式基本宣告失败。因此引入了右值引用。
2.3 右值引用实现的移动构造函数:我们直接上代码:
node(node&& otherNode) {
size = otherNode.size;
ptr = otherNode.ptr;
otherNode.ptr=nullptr;
}
node& operator=(node&& otherNode) {
size = otherNode.size;
ptr = otherNode.ptr;
otherNode.ptr=nullptr;
}
这样我们就成功接管了otherNode,没有使用任何拷贝。因此我们可以如下方式调用:
node a;
node b;
node c=a;
node d=std::move(b);
第三行为深拷贝,第四行为浅拷贝,直接接管b。
2.4 移动构造函数的注意事项:当我们没有定义移动构造函数时,我们采用移动拷贝时会执行默认的拷贝构造函数,即便我们使用下面写法:
node(node&& otherNode)=default;
它调用的仍是默认的拷贝构造函数node(const node& otherNode),当默认拷贝构造函数=delete时它也无法调用。但是当默认拷贝构造函数delete时,我们可以写移动构造函数并进行移动构造。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)