Chinaunix首页 | 论坛 | 博客
  • 博客访问: 287844
  • 博文数量: 148
  • 博客积分: 4365
  • 博客等级: 上校
  • 技术积分: 1566
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-05 21:38
文章分类
文章存档

2014年(2)

2013年(45)

2012年(18)

2011年(1)

2009年(54)

2008年(28)

我的朋友

分类: C/C++

2013-02-04 12:25:10

这个绝对是新增的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引用可以绑常量。

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

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是个右值引用,是说它绑定的是个右值,但其本身是左值,即下面这语句非法:
int &&rr3 = rr2;   // error: the expression rr2 is an lvalue!
即凡是可以 var_type var_name; 这样定义出来的变量(variable)其自身都是左值。



2. std::move相关。

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

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

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



3. move构造函数

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

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两种形式定义都不会成功。

	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个成员,自定义了一个,其余的最好都定义。

	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迭代器。

	vector ivec{1,2,3};
	vector ivec2(ivec.begin(),ivec.end());
	vector ivec3(make_move_iterator(ivec.begin()), make_move_iterator(ivec.end()));

	cout<
输出:3 3 3 3 4 。ivec2复制ivec的内容,ivec3则是“偷”了过来,虽然ivec的size仍是3,但内部已经不能保证,在ivec3压入元素0后,其size为4,但ivec的size仍为3。


阅读(5846) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~