前不久facebook在github上发布了一个c++工具库folly,其中的实现大量的使用了c++ 11的新特性,同时,gcc 从4.3版本开始支持c++ 11, 到现在的版本4.8,已经支持了绝大部分c++ 11的新特性(support list),让我感到时候有必要认真学习一下c++ 11了.关于11引进的新特性概述,已经有很多的文章了,如果你不了解,可以到这里.
今天主要来学习11版本中颇为重要的一个特性,move语义与右值引用,Stackoverflow 上有一篇相当不错的解释(原文),我觉得十分有必要翻译一下,一方面自我学习,一方面分享给大家。由于原文较长,这里分为基础和加深分两部分来翻译,对应作者的2个回答。以下是基础部分(加深部分会在三天内译出):
-----------------------------------------------------------------译文
我发现理解move语义最简单的方式是看一个样例,让我们从持有一块动态分配的内存的简单string类型开始:
2 #include <algorithm>
3
4 class string
5 {
6 char* data;
7
8 public:
9
10 string(const char* p)
11 {
12 size_t size = strlen(p) + 1;
13 data = new char[size];
14 memcpy(data, p, size);
15 }
既然我们要自己管理内存,那我们就应该遵守那三条原则(the rule of three),如果你不知道什么三条原则,去c++ 98标准里查找。下面我将推迟赋值运算符的实现,先来实现复制构造函数和析构函数:
2 {
3 delete[] data;
4 }
5
6 string(const string& that)
7 {
8 size_t size = strlen(that.data) + 1;
9 data = new char[size];
10 memcpy(data, that.data, size);
11 }
复制构造函数定义了怎样复制一个string对象。参数const string& that 可以为想要复制string的以下例子中的任何一种形式:
2 string b(x + y); // line 2
3 string c(some_function_returning_a_string()); // line 3
现在我们开始分析move语义。你会发现,只有第一行(line 1)的x深度拷贝是有必要的,因为我们可能会在后边用到x,如果x改变了,我们会很奇怪。你有没有注意到我刚刚把x说了三遍(如果包括这次的话,是四遍),每一遍都是说的同一个对象?我们把x的这种表达式叫做左值(lvalues)。
第二行和第三行的参数就不是左值而是右值,因为表达式产生的string对象是匿名对象,之后没有办法再使用了。右值就是指在下一个分号(更准确的说是在包含右值的完整表达式的最后)销毁的临时对象。这一点非常重要,因为我们可以在b和c的初始化过程中,对源string对象(参数)做任何想要做的事情,并不让用户感觉到。
C++ 11引入了一种新的机制叫做“右值引用”,以便我们通过重载直接使用右值参数。我们所要做的就是写一个以右值引用为参数的构造函数。在这个函数的内部,我们对参数所指向的对象做任何事情,只要我们保持他的合理性:
2 {
3 data = that.data;
4 that.data = 0;
5 }
我们在这里是怎么做的呢?我们没有深度拷贝堆内存中的数据,而是仅仅复制了指针,并把源对象的指针置空。事实上,我们“偷取”了属于源对象的内存数据。再次,问题的关键变成:无论在任何情况下,都不能让客户觉察到源对象被改变了。在这里,我们并没有真正的复制,所以我们把这个构造函数叫做“转移构造函数”(move constructor, 不知道译法是否确切),他的工作就是把资源从一个对象转移到另一个对象,而不是复制他们。
祝贺你,你现在对move语义有了基础的理解,我们继续来进行赋值操作符的实现。如果你不理解copy and swap惯用法,先去学习 一下,然后再回来,因为这是c++异常安全的一个非常精彩的惯用法。
2 {
3 std::swap(data, that.data);
4 return *this;
5 }
6 };
呃,就这些?右值引用在哪里?你可能会这样问。我的答案是:在这里,我们不需要
注意到我们是直接对参数that传值,所以that会像其他任何对象一样被初始化,那么确切的说,that是怎样被初始化的呢?对于C++ 98,答案是复制构造函数,但是对于C++ 11,编译器会依据参数是左值还是右值在复制构造函数和转移构造函数间进行选择。
如果是a=b,这样就会调用复制构造函数来初始化that(因为b是左值),赋值操作符会与新创建的对象交换数据,深度拷贝。这就是copy and swap 惯用法的定义:构造一个副本,与副本交换数据,并让副本在作用域内自动销毁。这里也一样。
如果是a = x + y,这样就会调用
1. "这决不会发生...", 我们不要这样自我欺骗, 特别是在编码时.
2. 传给断言的条件不应该有副作用.
3. 不要用断言代替真正的错误处理.
4. 如果你需要释放资源, 就让断言失败生成异常, longjump到某个退出点, 或是调用错误处理器.
5. 让断言开着. 即使你确实有性能问题, 也只关闭那些真的有很大影响的断言.
6. "海森堡虫子", 调试改变了被调试系统的行为. ASSERT(null != iter.nextElement());
下面是一些"不可能的事":
1. 一个月少于28天
2. stat(".",&sb) == -1
3. 在C++里: a=2; b=3;if (a+b!=5) exit(1);
4. 内角和不等于180的三角形
5. 没有60秒的一分钟
6. 在java 中: (a+1)<=a
PS: 刚开始看到, 真的很多都觉得是不可能的~~真当是知识面不够广~
"这决不会发生...", 我们不要这样自我欺骗, 特别是在编码时.
Michael.
Fortran和matlab语言中的多维数组存储方式为列优先原则,内循环最好是列循环;而c语言中的多维数组存储方式为行优先原则,内循环最好是行循环。下面介绍何为行优先存储,何为列优先存储。
例如二维数组Amn
(1)行优先顺序
将数组元素按行向量排列,第i+1个行向量紧接在第i个行向量后面。
【例】二维数组Amn的按行优先存储的线性序列为:
a11,a12,…,a1n,a21,a22,…,a2n,……,am1,am2,…,amn
行优先顺序推广到多维数组,可规定为先排最右的下标。
二维数组Amn地址计算公式(数组存储结构以C语言下标表示):
LOC(aij)=LOC(a11)+[(i-1)×n+j-1]×d
①LOC(a11)是开始结点的存放地址(即基地址)
②d为每个元素所占的存储单元数
(2)列优先顺序
将数组元素按列向量排列,第i+1个列向量紧接在第i个列向量后面。
【例】二维数组Amn的按列优先存储的线性序列为:
a11,a21,…,am1,a12,a22,…,am2,……,a1n,a2n,…,amn
列优先顺序推广到多维数组,可规定为先排最左的下标。
二维数组Amn地址计算公式(数组存储结构以C语言下标表示):
LOC(aij)=LOC(a11)+[(j-1)×m+i-1]×d
下面是用c语言测试存储方式对计算效率的影响
Program1. 不按计算机规定的存储方式编程
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
void main ()
{ int i,j;
float a[1000][5000];
clock_t beg, end;
double time;
beg=clock();
for (i=0; i<1000; i++) {
for (j=0; j<5000; j++) {
a[i][j]=0.5; }
for (j=0; j<5000; j++) {
for (i=0; i<1000; i++) {
a[i][j]=a[i][j]*a[i][j]+2.0*a[i][j]+10.0; }
end=clock();
time=(double)(end-beg)/CLOCKS_PER_SEC;
printf("Compute time is %f seconds\n",time);
}
Compute time is 0.063000 seconds
Program2.按计算机规定的存储方式编程
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
void main ()
{ int i,j;
float a[1000][5000];
clock_t beg, end;
double time;
beg=clock();
for (i=0; i<1000; i++) {
for (j=0; j<5000; j++) {
a[i][j]=0.5; }
for (i=0; i<1000; i++) {
for (j=0; j<5000; j++) {
a[i][j]=a[i][j]*a[i][j]+2.0*a[i][j]+10.0; }
end=clock();
time=(double)(end-beg)/CLOCKS_PER_SEC;
printf("Compute time is %f seconds\n",time);
}
Compute time is 0.046000 seconds
本文链接