当前课程知识点:C++语言程序设计基础 > 第6章 数组、指针与字符串(二) > 对象复制与移动 > 移动构造
移动构造
在现实中有很多这样的例子,我们将钱从一个账号转移到另一个账号,将手机SIM卡转移到另一台手机,将文件从一个位置剪切到另一个位置……移动构造可以减少不必要的复制,带来性能上的提升。
C++11标准中提供了一种新的构造方法——移动构造。
C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,我们没有必要复制对象——只需要移动它们。
C++11引入移动语义:
源对象资源的控制权全部交给目标对象
移动构造函数
问题与解决
当临时对象在被复制后,就不再被利用了。我们完全可以把临时对象的资源直接移动,这样就避免了多余的复制操作。
移动构造
什么时候该触发移动构造?
有可被利用的临时对象
移动构造函数:
class_name ( class_name && )
例:函数返回含有指针成员的对象(版本1)
使用深层复制构造函数
返回时构造临时对象,动态分配将临时对象返回到主调函数,然后删除临时对象。
#include<iostream>
using namespace std;
class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)){ //构造函数
cout << "Calling constructor..." << endl;
}
IntNum(const IntNum & n) : xptr(new int(*n.xptr)){//复制构造函数
cout << "Calling copy constructor..." << endl;
};
~IntNum(){ //析构函数
delete xptr;
cout << "Destructing..." << endl;
}
int getInt() { return *xptr; }
private:
int *xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
IntNum a;
return a;
}
int main() {
cout<<getNum().getInt()<<endl;
return 0;
}
运行结果:
Calling constructor...
Calling copy constructor...
Destructing...
0
Destructing...
例:函数返回含有指针成员的对象(版本2)
使用移动构造函数
将要返回的局部对象转移到主调函数,省去了构造和删除临时对象的过程。
#include<iostream>
using namespace std;
class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)){ //构造函数
cout << "Calling constructor..." << endl;
}
IntNum(const IntNum & n) : xptr(new int(*n.xptr)){//复制构造函数
cout << "Calling copy constructor..." << endl;
注:
•&&是右值引用
•函数返回的临时变量是右值
}
IntNum(IntNum && n): xptr( n.xptr){ //移动构造函数
n.xptr = nullptr;
cout << "Calling move constructor..." << endl;
}
~IntNum(){ //析构函数
delete xptr;
cout << "Destructing..." << endl;
}
private:
int *xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
IntNum a;
return a;
}
int main() {
cout << getNum().getInt() << endl; return 0;
}
运行结果:
Calling constructor...
Calling move constructor...
Destructing...
0
Destructing...
大家好
欢迎回来继续学习
C++语言程序设计
下面一个问题呢
是关于移动构造
移动在我们日常生活中呢
经常用到
比如说我们将钱
从一个帐号转到另外一个帐号
我们将手机sim卡
从一个手机拿出来
插到另外一个手机上面
还有呢
我们会将我们计算机中
存的文件
从一个位置剪切
贴到另外一个位置
也就是移动到另外一个位置
这些呢都是移动
移动的过程只是让我们这些对象
换了换地方
并没有去发生复制
那现在我们想一想
我们在程序中
去复制构造一个对象的时候
是不是有些时候
也不必要真的去发生复制呢
有的复制构造是必要的
我们确实需要另外一个副本出来
但是有些复制构造
可能就是不必要的
我们可能只是希望这个对象
换个地方 移动一下
那么接下来呢
我们就来看在程序中
怎么去实现移动构造
移动构造呢
是C++11标准中
提供的一种新的构造方法
以前如果要将原对象的状态
转移到目标对象
我们只能通过复制
那么现在呢
我们如果发现
我们只是需要转移
而不是真的需要复制
我们就可以有办法移动它了
那么C++11
引入的这种移动语义呢
实际上就是要将
语言对象的资源控制权
全部交给目标对象
我们首先来看看
这个移动构造函数
现在呢我们先通过这个图
让大家了解一下
复制构造和移动构造
它的差别是什么
我们先看左边这个过程
左边这个过程呢
当一个对象
需要进行复制构造的时候
那么它首先要复制出
相同的大小的内存空间单元来
大家看
在对象被复制以后
一个临时对象
还有a对象
它们各自占有不同的堆内存
同样大小的
也就是完全出来一个副本
而移动呢
就是让这个临时对象
它原本控制的这个内存空间
转移给对象a
那么就把它移动过去了
因为这种情况下我们觉得
这个临时对象
完成了复制构造以后
它就不需要了
那我们就没有必要
去首先产生一个副本
然后析构这个临时对象
这样费两道事
又多占内存空间
索性将临时对象
它的原本的这个资源
直接转给a对象就行了
这就是复制构造
和移动构造它的差别
那么我们什么时候
需要移动构造呢
就像刚才那个图里头表现的一样
如果呢这个临时对象
它即将消亡
并且它里面的资源
是需要被再利用的
这个时候
我们就可以触发移动构造
移动构造呢
是要通过移动构造函数来完成的
下面这个例题呢
就给大家演示了
这个移动构造的过程
在这个例子中呢
我们将调用一个函数
这个函数的返回值是个对象
而且这对象里面含有指针成员
这种情况下
怎么达到将对象返回呢
第一种办法
就是使用深层复制的构造函数
因为
如果是使用复制构造函数
去返回那个临时对象的话呢
是要发生复制的
这个复制
因为成员有指针
那么就必须用
深层复制的复制构造函数
那么另一种办法呢
就是使用移动构造函数
接下来呢
我们对比一下
这两种方法
它们的差别在什么地方
好 这是一个
我们用IntNum类去存放一个
它的数据是指向整数的指针
它的构造函数呢
大家看在初始化列表中
就是动态构造一个整数变量
用来初始化这个指针
动态构造的整数变量
它是返回一个指针类型的
指向Int的指针
去初始化xptr
它的复制构造函数
确实是深层复制
也是我们要干什么呢
大家看
构造一个Int的对象
构造一个新的Int对象
一定是构造Int对象
不是简单的复制指针
构造这个Int对象
它的初始值是什么呢
构造Int对象
它的初始值是
这个参数n的xptr指针
所指向的对象值
也就是说在这儿
做这个深层复制的复制构造呢
去构造一个新对象
这个新的Int对象
跟参数里面
那个指针指向的Int对象
它的值是一样的
构造成功以后
将这个指针用来初始化
当前对象的xptr
这是深层复制 确实
大家可以运行的时候
去观察一下
它确实做的是深层复制
接着这个析构函数里面呢
当然去删除
在构造函数中分配的内存空间
并且在构造函数
复制构造函数和析构函数中
都有输出提示信息
在运行的时候
我们能跟踪它的过程
知道它流程走到哪儿了
这里还有一个getInt函数
是返回这个指针所指向的那个值
不是返回指针本身
你看 有指针运算
返回指针所指向的值
好 现在呢
定义这么个函数getNum
它在里面定义了个局部变量
然后将这个局部对象
这儿定义一个局部对象
将局部对象作为结果返回
所以它的返回值是IntNum类型
在主函数中呢
主函数中调用getNum
它就返回了一个IntNum对象
再用这个对象去调用getInt
输出它里面的值
验证一下我是不是这个对象
返回回来了
我能不能看到它的值输出
然后我们看一下这个执行过程
执行过程
首先要调用这个getNum
就进到这里面来了
构造一个对象a
正常构造
调用构造函数
你看Calling constructor
在这儿
然后呢
要返回a的时候
a是一个非静态的局部变量
离开这个函数以后
它的寿命就结束了
既然你要返回a
在这个过程中
要构造一个临时无名对象
返回到主函数中
而a自己要被释放的
空间要被释放
所以在这个过程中
大家看到
调用了copy constructor
实际上在return a之前
就先要用a去作初始值
构造一个临时无名对象
调用了复制构造函数
然后离开这个函数的时候呢
a本身就被释放了
而临时无名对象呢
被带回到主函数中来
我们在调用getInt
去输出它的值
输出了一个0 看到了
最后呢
在离开主函数
也不用到离开主函数了
这一句执行完了
就是这一句执行完了
这个临时无名对象就不再存在
它就消亡了
所以看到了Destructing
那么这个过程中
大家是不是觉得有点
做了点无用功
挺繁琐的
这个又构造临时无名对象
用完了又消亡
麻烦不麻烦呢
其实呢
我们也不用构造临时无名对象了
a反正是要消亡的
我把这个要消亡的a
它所占用的资源
转移给临时对象不行吗
这样的方案就是
使用移动构造
我们看
在这里加了一个移动构造函数
这个移动构造函数
干了件什么事
好象干了件很危险的事 是不是
我们直接用参数对象里面的指针
来初始化当前对象的指针
这不是浅层复制吗
说了有指针成员
我们复制的时候
不能做这种浅层复制
怎么在这儿
恰恰做这种浅层复制呢
别着急
看函数体里面
做完这种指针对指针的复制
也就是把参数指针
所指向的对象转给了
当前正在被构造的这个指针
接着就把参数n里面的指针
给置为空指针
那么这个对象里面的指针
置为空指针了
将来析构函数
去析构它的时候
delete一个空指针
不发生任何事情
不会发生多次析构的事情
那么这个就是一个移动构造函数
移动构造函数
这个参数类型
两个and符号是什么呢
这表示是右值引用
什么是右值引用呢
即将消亡的值就是右值
函数返回的临时变量
也是右值
就是这样的单个的
这样的引用是可以绑定到
左值的
而这个引用呢
它可以绑定到即将消亡的对象
绑定到右值
所以呢
我们再看主函数
仍然是这样来调用
那么结果呢
它没有去调用复制构造函数
而调用了移动构造函数
我们看移动构造里面
没有发生内存分配
而这个对象a在离开的时候
消亡的时候
它是个即将消亡对象
它在消亡的时候呢
因为它的指针已经被置成空了
没有去释放内存空间
所以将一个即将消亡的对象
它的这个资源通过指针
初始化的方式
指针对指针初始化的方式
转移了
这个呢就是移动构造
它的效率要高一些
当然我们遇到这样的情况
不需要构造那么多对象
释放那么多对象的时候呢
我们就可以将要消亡的对象
它的资源转移过来就行了
这是效率高一些的办法
-导学
--第1章导学
-计算机系统简介
--计算机系统简介
--计算机系统简介 测试题
-计算机语言和程序设计方法的发展
--计算机语言和程序设计方法的发展 测试题
-面向对象的基本概念
--面向对象的基本概念 测试题
-程序的开发过程
--程序的开发过程
--程序的开发过程 测试题
-信息的表示和储存
--计算机的数字系统
--数据的编码表示
--信息的表示和储存 测试题
-实验指导
-导学
--第二章导学
-C++语言概述
--C++语言概述 测试题
-基本数据类型、常量、变量
--程序举例
--基本数据类型、常量、变量 测试题
-运算与表达式
--运算与表达式 测试题
-实验二:简单程序设计(上)
-数据的输入和输出
--数据的输入和输出
--数据的输入和输出 测试题
-选择结构
--if语句
--switch语句
--选择结构 测试题
-循环结构
--for语句
--循环结构 测试题
-自定义类型
--自定义类型
--自定义类型
-第2章小结
--第二章小结
-实验二:C++简单程序设计(下)
-导学
--导学
-函数定义
--函数定义
--函数定义 测试题
-函数调用
--例3-2
--例3-3
--例3-4
--例3-5
--例3-6
--函数调用 测试题
-嵌套与递归
--例3-9
--例3-10
--嵌套与递归 测试题
-函数的参数传递
--函数的参数传递
--函数的参数传递 测试题
-引用类型
--引用类型 测试题
-含有可变参数的函数
--含有可变参数的函数 测试题
-内联函数
--内联函数 测试题
-constexpr函数
--CONSTEXPR函数课后习题
-带默认参数值的函数
--带默认参数值的函数 测试题
-函数重载
--函数重载 测试题
-C++系统函数
--C++系统函数习题
-第3章小结
--第三章小结
-实验三(上)函数的应用
-实验三(下)函数的应用
-导学
--导学
-面向对象程序的基本特点
--面向对象程序的基本特点 测试题
-类和对象
--类和对象的定义
--类和对象 测试题
-构造函数
--构造函数基本概念
--委托构造函数
--复制构造函数
--构造函数 测试题
-析构函数
--析构函数
--析构函数 测试题
-类的组合
--类的组合
--类的组合程序举例
--前向引用声明
--类的组合 测试题
-UML简介
--UML简介
--UML简介课后习题
-结构体与联合体
--结构体与联合体 测试题
-枚举类
--枚举类
--枚举类 测试题
-第4章小结
--第四章小结
-实验四(上)
--实验四(上)
-实验四(下)
--实验四(下)
-导学
--导学
-标识符的作用域与可见性
--标识符的作用域与可见性 测试题
-对象的生存期
--对象的生存期
--对象的生存期 测试题
-类的静态成员
--类的静态成员 测试题
-类的友元
--类的友元 测试题
-共享数据的保护
--共享数据的保护 测试题
-多文件结构和预编译命令
--多文件结构和预编译命令 测试题
-第5章小结
--小结
-实验五
--实验五
-导学
--导学
-数组的定义与初始化
--数组的定义与使用
--一维数组应用举例
--数组的定义与初始化 测试题
-数组作为函数的参数
--数组作为函数的参数 测试题
-对象数组
--对象数组
--对象数组 测试题
-基于范围的for循环
-指针的定义和运算
--指针的定义和运算 测试题
-综合实例
--综合实例
-实验六(上)
--实验六上
-指针与数组
--指针数组
--指针与数组 测试题
-指针与函数
--指针类型的函数
--指向函数的指针
--指针与函数 测试题
-对象指针
--对象指针
--对象指针 测试题
-动态内存分配
--动态内存分配 测试题
-智能指针
--智能指针
-vector对象
--vector对象
--vector对象 测试题
-对象复制与移动
--移动构造
--对象复制与移动 测试题
-字符串
--C风格字符串
--string类
--字符串 测试题
-第6章小结
--第六章小结
-综合实例
--综合实例
-实验六(下)
--实验六(下)