右值引用、move与move constructor

2024-05-10 12:32
文章标签 引用 右值 constructor move

本文主要是介绍右值引用、move与move constructor,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文: http://blog.chinaunix.net/uid-20726254-id-3486721.html


这个绝对是新增的top特性,篇幅非常多。看着就有点费劲,总结更费劲。

原来的标准当中,参数与返回值的传值形式涉及到对象的复制,传值完成后,中间产生的临时对象又会马上被销毁,某些自定义的对象或者容器有很多元素时复制的开销非常大,而且例如IO对象或unique_ptr对象也不允许复制;传址在返回值的某些场景又会有局部对象的问题。c++11中新增了move的概念,用一个词描述就是"steal"。

1. 右值引用相关

个人理解,能出现在赋值运算符左侧即能被赋值的对象称之为左值,类似地右侧的赋值对象称为右值,都可以是单一变量或表达式。左值有其自己的identity,程序相对长期地为其永久或暂时地分配内存空间;右值多为临时变量(如某些表达式的中间值),生命期相对短暂(ephemeral),所有的字面常量均为右值。一个整形变量i,其为左值,我们可以赋给i任何其能存储的整型值;i+5这个表达式是右值,因为无论i是多少,你都不可能把20赋值给(i+5)。

右值引用必须绑定在右值上,书中的原话绑定的值是 an object that is about to be destroyed. 所以不能绑在左值上。

普通(regular)左值引用也不能绑定在右值上,const引用可以绑常量。

以前标准中的引用都是左值引用,运算符“&”,右值引用的运算符是"&&"。

1
2
3
4
5
6
int i = 42;
int &r = i;                   // ok: r refers to i
int &&rr = i;               // error: cannot bind an rvalue reference to an lvalue
int &r2 = i * 42;          // error: i * 42 is an rvalue
const int &r3 = i * 42; // ok: we can bind a reference to  const  to an rvalue
int &&rr2 = i * 42;      // ok: bind rr2 to the result of the multiplication
上面是几个书中的例子。 注意:rr2是个右值引用,是说它绑定的是个右值,但其本身是左值 ,即下面这语句非法:
1
int &&rr3 = rr2;   // error: the expression rr2 is an lvalue!
即凡是可以 var_type var_name; 这样定义出来的变量(variable)其自身都是左值。



2. std::move相关。

右值引用因为绑定对象即将被销毁,意味着没有人会继续访问他们,所以就可以把他们(的资源)steal过来。

虽然不能将右值引用绑在左值上,但通过利用utility头文件新增的函数模板move,它返回传入对象的右值引用,可以达到 steal的效果。

1
int &&rr3 = std::move(rr2);   // ok
再提醒:一旦使用了move,编译器就默认传入对象已经不打算使用了,是可以被销毁的,move之后该对象的值已经不确定,不要再访问。还有由于对象偷取与复制的差别巨大,不注意会产生非常难定位的bug,所以所有使用move的地方一定要使用全称std::move,给大家以提醒。(其实c++11在algorithm头文件也新增了一个move,参数与意义都与此截然不同)。



3. move构造函数

对象可以被复制构造,满足条件场景下,自然也就可以被“剪切”构造。move构造函数就用于此,它也属于copy-control系列函数,后者的5个成员现在更新为:拷贝构造函数,复制性质的赋值运算符,move构造函数,move性质的赋值运算符,析构函数。

1
2
3
4
5
6
7
8
9
10
class base
{
public :
     int * p;
     base():{p = new (...);}
     ~base(){ delete p;}
     base(base&& ref):p(ref.p){ref.p = nullptr;}
};
新创建对象的指针p接管了来源对象指针p所持有的资源,并将后者置空。特别注意move构造函数一定要来源对象内部存储资源的变量设置正确的状态,如指针置空等,避免来源对象析构时内存误释放。


对应的赋值运算符则一定要判断比较this指针和来源对象地址是否相同,进行自我move操作的保护。

move构造函数创建新对象后,来源对象并不是马上被销毁,但是出于一种析构函数随时可以运行的状态,不要再去访问它的值,这已经不可预计。


4. move构造函数的合成关系(喂,SBL你的inside c++11 object model啥时出版啊)

考构和其赋值运算符如果我们没有声明自己的,编译器会默认合成出来一个memberwise copy版本的。move构造函数和运算符却不是如此。如果类中有声明自己的考构和赋值运算符,或析构函数,则编译器不会合成默认的move操作函数,这时如果进行右值引用的move操作调用,实际触发的将会是拷贝构造函数。

当类中没有声明自己的任何一个copy-control函数,并且当所有非静态成员都可以move时,才会合成默认的move构造函数。

move构造函数和运算符从不会(never)隐式(implicit)被认为是delete,当我们声明其为default时,下列场景会变delete

(1)当类有某成员定义了其考构而没有m-con时,此类的m-con编译器认为是delete的,mv运算符同理,递归定义就是子成员的m-con合成不出来,该类的也肯定合成不出来。

(2)子成员的m-con或运算符是私有或delete,则此类的m-con和运算符是delete。

(3)类似考构,子成员或本身的析构是私有或delete,m-con为delete。

(4)类似赋值运算符,有const成员或引用成员,move运算符为delete。

经过试验,以下个人理解:在没有move成员时,都会调用考构成员,毕竟move风险太大。move成员的default最好不要随便加,没加时,调用std::move编译器还可以换成考构,加了后表示就要move,一旦有上面限制无法合成就会报错,虽然是编译的,不熟悉的搞起来还是有点麻烦。可能是类似提示,不知道为什么扯上了 constexpr

error: defaulted declaration 'base::base(const base&&)'
error: does not match expected signature 'constexpr base::base(base&&)'


BT的来了:move成员会反作用于考构成员,有定义move系列,没定义考构系列时,考构系列被认为是delete的。在base内定义实现了move系列成员,没有考构成员,下面的obj2两种形式定义都不会成功。

1
2
3
base obj1;
base obj2 (obj1);
base obj2= obj1;
提示都一样,左值不能绑到右值引用上,默认直接进入了move构造函数:


error: cannot bind 'base' lvalue to 'base&&'
error:   initializing argument 1 of 'base::base(base&&)'

结论:

(1)定义了move系列,则一定要定义考构系列。

(2)考构和move都有,左值引用进考构,右值引用进move.

(3)有考构没move,都进考构,即使通过std::move.

(4)copy-control系列的5个成员,自定义了一个,其余的最好都定义。

1
2
3
base obj2,obj1;
obj2 = base(5); //obj2 = std::move(base(5));
obj2 = obj1;
考构,mv都有,2行mv,3行考构运算符;


只有考构,都进考构运算符,无论第二行加不加std::move。


5. move迭代器,make_move_iterator,在iterator头文件,不导入也能用,把普通迭代器转成move迭代器。

1
2
3
4
5
6
7
8
9
     vector< int > ivec{1,2,3};
     vector< int > ivec2(ivec.begin(),ivec.end());
     vector< int > ivec3(make_move_iterator(ivec.begin()), make_move_iterator(ivec.end()));
     cout<<ivec.size()<<endl; 
    cout<<ivec2.size()<<endl;
    cout<<ivec3.size()<<endl;
    ivec3.push_back(0);
    cout<<ivec.size()<<endl;
    cout<<ivec3.size()<<endl;
输出:3 3 3 3 4 。ivec2复制ivec的内容,ivec3则是“偷”了过来,虽然ivec的size仍是3,但内部已经不能保证,在ivec3压入元素0后,其size为4,但ivec的size仍为3。

这篇关于右值引用、move与move constructor的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/976444

相关文章

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

Unity3D 运动之Move函数和translate

CharacterController.Move 移动 function Move (motion : Vector3) : CollisionFlags Description描述 A more complex move function taking absolute movement deltas. 一个更加复杂的运动函数,每次都绝对运动。 Attempts to

JavaSE(十三)——函数式编程(Lambda表达式、方法引用、Stream流)

函数式编程 函数式编程 是 Java 8 引入的一个重要特性,它允许开发者以函数作为一等公民(first-class citizens)的方式编程,即函数可以作为参数传递给其他函数,也可以作为返回值。 这极大地提高了代码的可读性、可维护性和复用性。函数式编程的核心概念包括高阶函数、Lambda 表达式、函数式接口、流(Streams)和 Optional 类等。 函数式编程的核心是Lambda

17 通过ref代替DOM用来获取元素和组件的引用

重点 ref :官网给出的解释是: ref: 用于注册对元素或子组件的引用。引用将在父组件的$refs 对象下注册。如果在普通DOM元素上使用,则引用将是该元素;如果在子组件上使用,则引用将是组件实例: <!-- vm.$refs.p will be the DOM node --><p ref="p">hello</p><!-- vm.$refs.child will be the c

12C 新特性,MOVE DATAFILE 在线移动 包括system, 附带改名 NID ,cdb_data_files视图坏了

ALTER DATABASE MOVE DATAFILE  可以改名 可以move file,全部一个命令。 resue 可以重用,keep好像不生效!!! system照移动不误-------- SQL> select file_name, status, online_status from dba_data_files where tablespace_name='SYSTEM'

【JAVA入门】Day35 - 方法引用

【JAVA入门】Day35 - 方法引用 文章目录 【JAVA入门】Day35 - 方法引用一、方法引用的分类1.引用静态方法2.引用成员方法2.1 引用其他类的成员方法2.2 引用本类和父类的成员方法2.3 引用构造方法2.4 使用类名引用成员方法2.5 引用数组的构造方法 二、方法引用的例题         方法引用就是“把已经有的方法当作函数式接口中抽象方法的方法

gcc 编译器对 sqrt 未定义的引用

man sqrt  Link with -lm. gcc -o test test.c -lm 原因:缺少某个库,用 -l 参数将库加入。Linux的库命名是一致的, 一般为 libxxx.so, 或 libxxx.a, libxxx.la, 要链接某个库就用   -lxxx,去掉头 lib 及 "." 后面的 so, la, a 等即可。 常见的库链接方法为

【JavaScript】基本数据类型与引用数据类型区别(及为什么String、Boolean、Number基本数据类型会有属性和方法?)

基本数据类型   JavaScript基本数据类型包括:undefined、null、number、boolean、string。基本数据类型是按值访问的,就是说我们可以操作保存在变量中的实际的值。 1)基本数据类型的值是不可变的 任何方法都无法改变一个基本类型的值,比如一个字符串: var name = "change";name.substr();//hangconsole.log

被审稿人批得体无完肤?参考文献这样引用就对了!

我是娜姐 @迪娜学姐 ,一个SCI医学期刊编辑,探索用AI工具提效论文写作和发表。 审稿人对参考文献引用提出质疑,在comments中还挺常见的。一般来说,是最新的、相关的、重要的文献引用缺失。此外,如果仔细分析引文来源,娜姐还发现有些常见问题: 1 引用不全面。 比如,声称某药物有ABCD四个功能,但是引文只证明了ABC三个功能。 2 引用不准确。 引文中上升趋势是25%,但是你

C++中的左值(Lvalue)和右值(Rvalue)详解

C++中的左值(Lvalue)和右值(Rvalue)详解 在C++中,左值(Lvalue)和右值(Rvalue)的概念是理解表达式和变量的重要基础。为了提高C++的性能和灵活性,C++11引入了一些新的特性,如右值引用和移动语义,因此了解左值和右值的区别对于编写高效的C++代码至关重要。 1. 什么是左值和右值? 左值(Lvalue):指的是有持久存储的对象。它们的值可以被获取和修改,并且在