C++11的新特性学习体会

2024-04-05  本文已影响0人  东方胖

lambda表达式

auto 关键字

类型推导
好处之一是可以在某些地方省去繁复地写一些东西
比如遍历一个容器
以前(C++98) 是这样

std::vector<int> v;
//
...
// 
std::vector<int>::const_iterator it = v.begin();
for ( it; it != v.end(): it++) {
}

在C++11中用auto可以简单地写成

for(auto it = v.begin(); it != it.end(); ++it) {
}

编译器根据auto可以做类型推导,判断出 it的类型就是 v 类型内置的迭代器 const_iterator

auto 也可以这么用

auto i = 1; // 推导 i为 int

因此auto有点动态类型的特点,但是auto的出现并没有让C++11编程动态类型语言,为什么?因为尽管auto可以类型推导,但是这个动作是出现在编译阶段而非运行阶段,但是Python的变量能够在运行时进行推导。

评述
以上的一点auto的优势的意义,因为

std::vector<int>::const_iterator it = v.begin();

的表达在很多时候实际上没有什么太大的意义,我们知道 it 是容器内的一个迭代器,但是不太确定它到底是写成什么样,写出来非常冗长,同时好像只是表明我们知道它,当然直接告诉编译器,这是一个这样的类型,编译器可以免去推导的工作,但是既然编译器擅长此事,我们可以把这种繁琐的事情交给它,本质上只是增加了编译的负担,但是运行效果理论上是一样的。
另一个关键是,同行之间代码的阅读沟通,auto的存在让阅读代码者不知道这个变量的类型,好像缺了什么,带了含糊,实际上也不然——既然代码作者可以让编译器去推导,阅读者自然也无须明确知道知道这个变量确切的表达式。

在一个项目中所有变量声明都用auto,会发生什么?
第一,代码可读性变差,有的地方变量的定义相当遥远,无法快速知道变量的定义
第二,代码的维护性必然变差,因为auto出现在全局文件中,一旦使用这个变量声明,这里的修改将会殃及所有引用该变量的代码块,这里用“殃及”这个词,是因为,auto的右半部分的变化,会随时改变auto的推导结果,而其它使用该变量的上下文可能并不知道这个变化的用意所在

以上一些讨论表明auto的使用需要克制而为。那么实践应当如何,auto的引入是为了解决某些代码繁冗的问题,而不是为了引入更多歧义,所以它的用法更多是关于什么时候应该使用

类型推导decltype

委托构造函数

nullptr

右值引用和移动构造函数

区分左值和右值

struct ComplexNumber {
double real;
double image;
}
double real(const ComplexNumber number) {
  return number.real;
}

double image(const ComplexNumber& number) {
    return number.image;
}

ComplexNumber add(const ComplexNumber& number1, const ComplexNumber& number2) {
    ComplextNumber ans;
ans.real = number1.real + number2.real;
ans.image = number2.real + number2.image;
   return ans;
}

real方法的参数是一个传值的模型,这意味着传递参数时会调用一次复制构造函数——这里是默认生成的,所以可以编译通过;
image方法是按引用传递,直接返回对象的虚部,而不会调用复制构造函数;
add 方法虽然两个参数都按引用传递,但是因为局部构造了一个临时对象,并且在return 时返回了ans,这时,add 调用一次ComplexNumber的构造函数,额外有一次复制构造函数,因为return时复制了一个临时对象。
除此以外,临时对象的申请和析构,增加了析构函数的调用次数,如果,成员变量涉及内存分配,就会因为传值的使用增加了内存分配和申请的开销。

以上 real 方法多余的复制构造函数调用可以用按引用传递参数的方式解决,而add方法呢?似乎束手无策,我们再C98里,如果 ComplexNumber 是一个复制开销极大的对象,只能把函数声明成这样
void add(const ComplexNumber& number1, const ComplexNumber& number2, ComplexNumber& result);
将结果带到 result变量中,这样写很不直接,client代码写得很不自然

移动构造函数的目的

移动语义
大概意思是,发生移动构造时,本来需要调用复制构造函数的时候,将原临时对象指向的内存搬到了自己的指针指向之下,而原来的临时对象(右值)的指针标记为 nullptr这样就完成了移动。这个操作减少了一个内存分配和析构的开销过程,因而效率是有保证的。

例子:

class ExampleObject {
public:
ExampleObject() : data(new int(10)){
  cout << " Call construct: " << ++n_construct << endl;
}
ExampleObject(const ExampleObject & other) : data(new int(*other.data)){
  cout << " Call copy construct: " << ++n_copy<< endl;
}

ExampleObject(ExampleObject && other): data(other.data) {
other.data = nullptr;
cout << " Call move construct: " << ++n_move << endl;
}
int * data;
static int n_move;
static int n_copy;
static int n _construct;
static int n_descons;
} ;

int ExampleObject::n_move = 0;
int ExampleObject::n_copy = 0;
int ExampleObject::n_construct = 0;
int ExampleObject::n_descons = 0;

ExampleObject GetTmpO() {
ExampleObject o;
cout << "res from " << __func__ <<  ":" << hex << o.data << endl;
return o;
}
int main() {
  ExampleObject o = GetTmpO();
  cout << "res from " << __func__ ":" << hex << o.data << endl;
}

CompexNumber a(1,2);
CompexNumber b(3, 4);
CompexNumber result;
add(a, b result) ;
// operate result) 

我们希望可以这样写

CompexNumber result = add(a, b); 

同时不损失性能
这就给新标准提出了一个需求。
C++11的移动构造函数就是为了解决这个问题的。

智能指针的进化

多线程支持

小特性集合

移位符号和模板尖括号的兼容

在上一个版本 C98中,编译器无法通过如下的表达

template <class T> class TypeA;
template <int i > class T;

TypeA<T<1> > t1; // 编译成功
TypeA<T<2>> 22; // 编译失败

又如类型转换表达

static_cast<std::vector<int>>(var);//无法编译

C++11通过标准化约定以上的表达都可以行得通,而不用增加额外的括号,
评述
实际上 对于模板嵌套和右移符号冲突的问题,大多数并不是很尖锐,完全可以让编译器做出智能判断,在极少数的模板尖括号内使用位移符号的情况,只是一种很刁钻的场景。如

template <int i> class T;
T< 1>> 5> t; //C11无法通过,C98可以。

要使得C11也顺利通过只需加一个括号

T< (1 >> 5)> t; 

在编程用户端减少一些无聊的分歧是必要的,因此C++加入了这样的需求
另一个例子并不是出现在C++11 标准中

禁用构造函数,或拷贝函数的一些方法

上一篇 下一篇

猜你喜欢

热点阅读