当前课程知识点:C++语言程序设计基础 >  第6章 数组、指针与字符串(二) >  对象复制与移动 >  移动构造

返回《C++语言程序设计基础》慕课在线视频课程列表

移动构造在线视频

移动构造

移动构造

在现实中有很多这样的例子,我们将钱从一个账号转移到另一个账号,将手机SIM卡转移到另一台手机,将文件从一个位置剪切到另一个位置……移动构造可以减少不必要的复制,带来性能上的提升。

问题与解决


移动构造

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风格字符串

返回《C++语言程序设计基础》慕课在线视频列表

移动构造课程教案、知识点、字幕

大家好

欢迎回来继续学习

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在离开的时候

消亡的时候

它是个即将消亡对象

它在消亡的时候呢

因为它的指针已经被置成空了

没有去释放内存空间

所以将一个即将消亡的对象

它的这个资源通过指针

初始化的方式

指针对指针初始化的方式

转移了

这个呢就是移动构造

它的效率要高一些

当然我们遇到这样的情况

不需要构造那么多对象

释放那么多对象的时候呢

我们就可以将要消亡的对象

它的资源转移过来就行了

这是效率高一些的办法

C++语言程序设计基础课程列表:

第1章 绪论

-导学

--第1章导学

-计算机系统简介

--计算机系统简介

--计算机系统简介 测试题

-计算机语言和程序设计方法的发展

--计算机语言和程序设计方法的发展

--计算机语言和程序设计方法的发展 测试题

-面向对象的基本概念

--面向对象的基本概念

--面向对象的基本概念 测试题

-程序的开发过程

--程序的开发过程

--程序的开发过程 测试题

-信息的表示和储存

--计算机中的信息与存储单位

--计算机的数字系统

--数据的编码表示

--信息的表示和储存 测试题

-实验指导

--实验一:VS开发环境介绍

第2章 C++简单程序设计(一)

-导学

--第二章导学

-C++语言概述

--C++的特点和程序实例

--C++字符集和词法记号

--C++语言概述 测试题

-基本数据类型、常量、变量

--基本数据类型、常量、变量

--程序举例

--基本数据类型、常量、变量 测试题

-运算与表达式

--算术运算与赋值运算

--逗号运算、关系运算、逻辑运算和条件运算

--Sizeof运算、位运算

--运算优先级、类型转换

--运算与表达式 测试题

-实验二:简单程序设计(上)

--实验二:简单程序设计(上)

第2章 C++简单程序设计(二)

-数据的输入和输出

--数据的输入和输出

--数据的输入和输出 测试题

-选择结构

--if语句

--switch语句

--选择结构 测试题

-循环结构

--循环结构——while语句

--do-while语句

--for语句

--嵌套的控制结构、其他控制语句

--循环结构 测试题

-自定义类型

--自定义类型

--自定义类型

-第2章小结

--第二章小结

-实验二:C++简单程序设计(下)

--实验二C++简单程序设计(下)

第3章 函数

-导学

--导学

-函数定义

--函数定义

--函数定义 测试题

-函数调用

--函数调用(例3-1)

--例3-2

--例3-3

--例3-4

--例3-5

--例3-6

--函数调用 测试题

-嵌套与递归

--函数的嵌套调用(例3-7)

--函数的递归调用(例3-8)

--例3-9

--例3-10

--嵌套与递归 测试题

-函数的参数传递

--函数的参数传递

--函数的参数传递 测试题

-引用类型

--引用类型(例3-11)

--引用类型 测试题

-含有可变参数的函数

--含有可变参数的函数

--含有可变参数的函数 测试题

-内联函数

--内联函数(例3-14)

--内联函数 测试题

-constexpr函数

--constexpr函数

--CONSTEXPR函数课后习题

-带默认参数值的函数

--带默认参数值的函数

--默认参数值例(3-15)

--带默认参数值的函数 测试题

-函数重载

--函数重载(例3-16)

--函数重载 测试题

-C++系统函数

--C++系统函数(例3-17)

--C++系统函数习题

-第3章小结

--第三章小结

-实验三(上)函数的应用

--实验三(上)函数的应用

-实验三(下)函数的应用

--实验三(下)函数的应用

第4章 类与对象

-导学

--导学

-面向对象程序的基本特点

--面向对象程序的基本特点

--面向对象程序的基本特点 测试题

-类和对象

--类和对象的定义

--类和对象的程序举例

--类和对象 测试题

-构造函数

--构造函数基本概念

--构造函数例题(1)——例4-1

--构造函数例题(2)——例4-2

--委托构造函数

--复制构造函数

--复制构造函数调用举例

--构造函数 测试题

-析构函数

--析构函数

--析构函数 测试题

-类的组合

--类的组合

--类的组合程序举例

--前向引用声明

--类的组合 测试题

-UML简介

--UML简介

--UML简介课后习题

-结构体与联合体

--结构体(例4-7)

--联合体(例4-8)

--结构体与联合体 测试题

-枚举类

--枚举类

--枚举类 测试题

-第4章小结

--第四章小结

-实验四(上)

--实验四(上)

-实验四(下)

--实验四(下)

第5章 数据的共享与保护

-导学

--导学

-标识符的作用域与可见性

--标识符的作用域与可见性

--标识符的作用域与可见性 测试题

-对象的生存期

--对象的生存期

--对象的生存期 测试题

-类的静态成员

--静态数据成员(例5-4)

--静态函数成员(例5-5)

--类的静态成员 测试题

-类的友元

--类的友元(例5-6)

--类的友元 测试题

-共享数据的保护

--共享数据的保护(例5-7)

--共享数据的保护 测试题

-多文件结构和预编译命令

--多文件结构和预编译命令(例5-10)

--多文件结构和预编译命令 测试题

-第5章小结

--小结

-实验五

--实验五

第6章 数组、指针与字符串(一)

-导学

--导学

-数组的定义与初始化

--数组的定义与使用

--数组的储存与初始化

--一维数组应用举例

--数组的定义与初始化 测试题

-数组作为函数的参数

--数组作为函数参数(例6-2)

--数组作为函数的参数 测试题

-对象数组

--对象数组

--对象数组 测试题

-基于范围的for循环

--基于范围的for循环

-指针的定义和运算

--指针的概念、定义和指针运算

--指针的初始化和赋值

--指针的算术运算、关系运算

--指针的定义和运算 测试题

-综合实例

--综合实例

-实验六(上)

--实验六上

第6章 数组、指针与字符串(二)

-指针与数组

--用指针访问数组元素

--指针数组

--指针与数组 测试题

-指针与函数

--以指针作为函数参数

--指针类型的函数

--指向函数的指针

--指针与函数 测试题

-对象指针

--对象指针

--对象指针 测试题

-动态内存分配

--动态分配与释放内存

--申请和释放动态数组(一)

--申请和释放动态数组(二)

--动态内存分配 测试题

-智能指针

--智能指针

-vector对象

--vector对象

--vector对象 测试题

-对象复制与移动

--深层复制与浅层复制

--移动构造

--对象复制与移动 测试题

-字符串

--C风格字符串

--string类

--字符串 测试题

-第6章小结

--第六章小结

-综合实例

--综合实例

-实验六(下)

--实验六(下)

移动构造笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。