C++ 11和C++98相比有哪些新特性

C++11标准提供了许多有用的新特性。这篇文章特别针对使C++11和C++98相比看上去像一门新语言的特性,因为:

C++11改变了书写C++代码的风格和习惯,也改变了设计C++库的方式。例如,你会看到更多的被当作参数和返回值的智能指针,还有按值(by value)返回巨大对象的函数。

它们被使用的非常广泛,在大多数代码中你都能看到它们。举个例子,在现代C++中几乎每5行C++代码你就能看到auto关键字。

还有一些其它的非常好的C++11新特性,但先把这篇文章所描述的新特性熟悉起来把,因为这些被广泛使用的特性展示了为什么C++11代码是简洁的,安全的和快速的,就像其它现代主流开发语言一样,并且性能和传统C++一样强大。

1. Auto

在任何可能的时候使用auto。因为有两个原因。第一,非常明显,能够避免重复输入我们已经声明的并且编译器已经认识的类型名称,这是非常方便的。

// C++98
map<int,string>::iterator i = m.begin();
double const xlimit = config["xlimit"];
singleton& s = singleton::instance();
// C++11
auto i = begin(m);
auto const xlimit = config["xlimit"];
auto& s = singleton::instance();

第二,当有遇到一个你不知道或者无法用语言表达的类型时,auto就不仅仅是使用方便这么简单了,比如,大多数lambda函数的类型,你不能够容易的将其类型拼写出来甚至根本就不能够写出来。

// C++98
binder2nd< greater > x = bind2nd( greater(), 42 );
// C++11
auto x = [](int i) { return i > 42; };

注意,使用auto并没有修改代码的语义。代码仍然是静态输入(statically typed)的,并且每个表达式都干净利落;只是不再强制我们多余的重新声明类型的名称。

一些人开始的时候害怕使用auto,因为给人的感觉像是并没有声明(重新声明)我们想要的类型,这意味着我们可能会突然得到一个不同的类型。如果你想显示的做强制类型转换,这没有问题;声明目标类型就可以了。但是在大部分情况下,使用auto就足够了,由于出现错误而得到另外一个类型的情况很少见,在使用强静态类型(strong static typing)情况下,如果类型出现错误编译器就会告诉你。

2. 智能指针,no delete

总是使用智能指针,不要用原生指针和delete。除非需要实现你自己的底层数据结构(把原生指针很好的封装在类(class boundary)中

如果你知道你是另外一个对象的唯一拥有着,使用unique_ptr来表示唯一的拥有权。一个"new T"表达式能很快的初始化一个拥有 这个智能指针的对象,特别是unique_ptr。典型的例子是指向实现的指针(Pimpl Idiom):

// C++11 Pimpl idiom: header file
class widget {
public:
  widget();
  // ... (see GotW #100) ...
private:
  class impl;
  unique_ptr<impl> pimpl;
};
// implementation file
class widget::impl { /*...*/ };
widget::widget() : pimpl{ new impl{ /*...*/ } } { }
// ...

使用shared_ptr来表示共享所有权(shared ownership)。使用make_shared来创建共享对象更好。

// C++98
widget* pw = new widget();
:::
delete pw;
// C++11
auto pw = make_shared<widget>();

使用weak_ptr来打破循环和表示可选性(比如实现一个对象缓存)

// C++11
class gadget;
class widget {
private:
  shared_ptr<gadget> g; // if shared ownership
};
class gadget {
private:
  weak_ptr<widget> w;
};

如果你了解到另外一个对象比你的生存周期要长,并且你想观察这个对象,那么使用原生指针(raw pointer)。

// C++11
class node {
 vector<unique_ptr<node>> children;
 node* parent;
public:
 :::
};

3. Nullptr

用nullptr来表示一个空指针,不要再使用数字0或者宏NULL来表示空指针了,因为这些是模棱两可的,既能表示整形也可表示指针。

// C++98
int* p = 0;
// C++11
 int* p = nullptr;
 
4. Range for

对一个范围内的元素进行有序访问,基于range的for循环会是更方便的用法。

// C++98
  for( vector<int>::iterator i = v.begin(); i != v.end(); ++i ) {
  total += *i;
 } 
// C++11
 for( auto d : v ) {
     total += d;
}

5. 非成员begin和end

使用非成员函数begin(x)和end(x)(不是x.begin()和x.end()),因为begin(x)和end(x)是可扩展的,能同所有容器类型一块工作——甚至数组也可以——并不是只针对提供了STL风格的x.begin()和x.end()成员函数的容器。

如果你正在使用一个非STL集合类型,这个类型提供迭代器但不是STL风格的x.begin()和x.end(),你可以对他的非成员函数begin()和end()进行重载,这样你就可以使用同STL容器同样的风格进行编码。标准中举了一个例子:数组,并且提供了对象的begin和end函数:

vector<int> v;
 int a[100]; 
  // C++98
 sort( v.begin(), v.end() );
  sort( &a[0], &a[0] + sizeof(a)/sizeof(a[0]) ); 
  // C++11
 sort( begin(v), end(v) );
 sort( begin(a), end(a) );

6. Lambda函数和算法

Lambda表达式改变了游戏规则,它会时不时的改变你的编码方式,这种方式优雅并且快速。Lambda使现存STL算法实用性提高了百倍。
新增加的C++库的设计都以支持lambad表达式为前提(例如:PPL),甚至有一些库需要通过你编写lambda表达式来使用库(例如:c++ AMP)。
这里有个例子,找到v中的>X并且<Y的第一个元素。在C++11中,最简单并且干净的代码是使用标准算法。

// C++98: write a naked loop (using std::find_if is impractically difficult)
 vector<int>::iterator i = v.begin(); // because we need to use i later
 for( ; i != v.end(); ++i ) {
     if( *i > x && *i < y ) break;
 } 
 // C++11: use std::find_if
 auto i = find_if( begin(v), end(v), [=](int i) { return i > x && i < y; } );

想使用一个循环或者类似的语言特性(language feature)但实际上在该语言中并不存在,怎么办?将其实现成模板函数(库算法)就可以了,多亏了lambda,使用它就像是用一个语言特性一样的方便,但是更灵活,因为它确实是一个库而不是一个固定的语言特性。

// C#
  lock( mut_x ) {
     ... use x ...
  } 
  // C++11 without lambdas: already nice, and more flexible (e.g., can use timeouts, other options)
 {
      lock_guard<mutex> hold { mut_x };
      ... use x ...
 }
 // C++11 with lambdas, and a helper algorithm: C# syntax in C++
// Algorithm: template<typename T> void lock( T& t, F f ) { lock_guard hold(t); f(); }
 lock( mut_x, [&]{
     ... use x ...
 });

熟悉一下lambda吧,你会发现他们很有用,并不只是在c++中,它们已经在几个主流语言中得到支持并且流行开来。

7. Move/&&

把move当作是对拷贝的优化最合适不过了,虽然它也包含其他方面的东西(像完美转发(perfect forwarding))
move语义改变了我们设计API的方式。我们会越来越多的将函数设计成return by value。

// C++98: alternatives to avoid copying
  vector<int>* make_big_vector(); // option 1: return by pointer: no copy, but don't forget to delete
 :::
  vector<int>* result = make_big_vector(); 
  void make_big_vector( vector<int>& out ); // option 2: pass out by reference: no copy, but caller needs a named object
  :::
  vector<int> result;
  make_big_vector( result ); 
 // C++11: move
 vector<int> make_big_vector(); // usually sufficient for 'callee-allocated out' situations
 :::
 auto result = make_big_vector(); // guaranteed not to copy the vector

如果你想获得比copy更高效的办法,对你的类型使用move语义吧。

8. 统一初始化和初始化列表

没有发生变化的:当初始化一个non-POD或者auto的本地变量时,继续使用熟悉的不带额外花括号{}的=语法。

// C++98 or C++11
 int a = 42;        // still fine, as always 
 // C++ 11
auto x = begin(v); // no narrowing or non-initialization is possible

在其他情况中(特别是随处可见的使用()来构造对象),使用花括号{}会更好。使用花括号{}能避免一些潜在的问题:你不会突然得到一个收缩转换(narrowing conversions)后的值(比如,float转换成int),也不会有偶尔突发的未初始化POD成员变量或者数组的存在,也能避免在c++98中会碰到的奇怪事:你的代码编译没问题,你需要的是变量但实际上你声明了一个函数,这都源于C++声明语法的模糊不清,Scott Meyers的著名说法:“C++最令人苦恼的解析”。通过使用新风格的语法上面解析问题会不复存在。

// C++98
 rectangle       w( origin(), extents() );   // oops, declares a function, if origin and extents are types
  complex<double> c( 2.71828, 3.14159 );
  int             a[] = { 1, 2, 3, 4 };
  vector<int>     v;
  for( int i = 1; i <= 4; ++i ) v.push_back(i); 
  // C++11
  rectangle       w   { origin(), extents() };
 complex<double> c   { 2.71828, 3.14159 };
 int             a[] { 1, 2, 3, 4 };
 vector<int>     v   { 1, 2, 3, 4 };
 
新的{}语法在几乎任何地方都能出色的工作。

// C++98
 X::X( /*...*/ ) : mem1(init1), mem2(init2, init3) { /*...*/ }
// C++11
 X::X( /*...*/ ) : mem1{init1}, mem2{init2, init3} { /*...*/ }

最后,有时候传递不带(type-named temporary)的函数参数是很方便的:

void draw_rect( rectangle );
 
// C++98
 draw_rect( rectangle( myobj.origin, selection.extents ) );
 // C++11
draw_rect( { myobj.origin, selection.extents } );

我不喜欢使用花括号{}的唯一地方是在初始化一个非POD变量的时候,像 auto x= begin(v);使用花括号会使代码不必要的丑陋,因为我知道了它是一个类类型,所以我不必担心收缩转换,并且现代编译器已经对额外的拷贝(或者额外move,如果类型是move-enabled)进行了优化。

(0)

相关推荐

  • C++ 11和C++98相比有哪些新特性

    C++11标准提供了许多有用的新特性.这篇文章特别针对使C++11和C++98相比看上去像一门新语言的特性,因为: C++11改变了书写C++代码的风格和习惯,也改变了设计C++库的方式.例如,你会看到更多的被当作参数和返回值的智能指针,还有按值(by value)返回巨大对象的函数. 它们被使用的非常广泛,在大多数代码中你都能看到它们.举个例子,在现代C++中几乎每5行C++代码你就能看到auto关键字. 还有一些其它的非常好的C++11新特性,但先把这篇文章所描述的新特性熟悉起来把,因为这些

  • C++第11版本中的一些强大的新特性小结

    Auto Type Deduction 自动类型推导 auto 关键字让用户得以使用 C++ 内置的类型推导特性. std::string something = somethingthatreturnsastring.getString(); auto something = somethingthatreturnsastring.getString(); Auto 关键字会对上述自变量(something)进行自动推导,得出其应该是 string 类型的结论,并在 auto 出现的地方用正确

  • C++11的新特性简单汇总介绍 (一)

    什么是C++11 C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩展了C++的标准程序库(STL),并入了大部分的C++ Technical Report 1(TR1)程序库(数学的特殊函数除外). C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto.decltype,和模板的大量改进. 1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己

  • c++11 新特性——智能指针使用详解

    c++11添加了新的智能指针,unique_ptr.shared_ptr和weak_ptr,同时也将auto_ptr置为废弃(deprecated). 但是在实际的使用过程中,很多人都会有这样的问题: 不知道三种智能指针的具体使用场景 无脑只使用shared_ptr 认为应该禁用raw pointer(裸指针,即Widget*这种形式),全部使用智能指针 初始化方法 class A { public: A(int size){ this->size = size; } A(){} void Sh

  • C++11新特性之列表初始化的具体使用

    目录 统一的初始化方法 列表初始化的一些使用细节 初始化列表 1.任何长度的初始化列表 2.std::initialzer-list的使用细节 列表初始化防止类型收窄 在我们实际编程中,我们经常会碰到变量初始化的问题,对于不同的变量初始化的手段多种多样,比如说对于一个数组我们可以使用 int arr[] = {1,2,3}的方式初始化,又比如对于一个简单的结构体: struct A { int x; int y; }a={1,2}; 这些不同的初始化方法都有各自的适用范围和作用,且对于类来说不能

  • 关于iOS 11的一些新特性适配实践总结

    前言 iOS 11 已经发布了一段时间了,随手记团队也早早的完成了适配.在这里,我们做了点总结,与大家一起分享一下关于 iOS 11 一些新特性的适配. UIView & UIViewController Layout Margins iOS 11 中,官方提供了一种新的布局方法--通过 layout margins 进行布局.官方文档 Positioning Content Within Layout Margins 称,使用这种布局可以保证各个 content 之间不会相互覆盖. 总的来说,

  • C++11新特性中auto 和 decltype 区别和联系

    C++11新特性中auto 和 decltype 区别和联系 一. auto简介 编程时候常常需要把表达式的值付给变量,需要在声明变量的时候清楚的知道变量是什么类型.然而做到这一点并非那么容易(特别是模板中),有时候根本做不到.为了解决这个问题,C++11新标准就引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型.和原来那些只对应某种特定的类型说明符(例如 int)不同.auto 让编译器通过初始值来进行类型推演.从而获得定义变量的类型,所以说 auto 定义的变量必须有初始

  • C++11的新特性简单汇总介绍 (二)

    1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; for(int x: vec) { cout<<x<<endl; } 2. 尾置返回类型 要想引入尾置类型,我们还得从复杂的类型声明说起.如果我们需要定义一个含有10个int元素的数组,一般是这样的: int arr[10] = {0}; 如果要定义指向这个数组的指针呢: 复制代码 代

  • 浅析C++11新特性的Lambda表达式

    lambda简介 熟悉Python的程序员应该对lambda不陌生.简单来说,lambda就是一个匿名的可调用代码块.在C++11新标准中,lambda具有如下格式: [capture list] (parameter list) -> return type { function body } 可以看到,他有四个组成部分: 1.capture list: 捕获列表 2.parameter list: 参数列表 3.return type: 返回类型 4.function body: 执行代码

  • C++ 11新特性之大括号初始化详解

    本文主要给大家介绍了关于C++11新特性之大括号初始化的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: C++11之前,C++主要有以下几种初始化方式: //小括号初始化 string str("hello"); //等号初始化 string str="hello"; //大括号初始化 struct Studnet{ char* name; int age; }; Studnet s={"dablelv",18}; //

随机推荐