“C++11就像一门新的语言。” – Bjarne Stroustrup
C++11标准推出了很多有用的新特性,本文特别关注那些相比C++98更像是一门新语言的特性,理由是:
(1)这些特性改变了编写C++程序使用的代码风格和习语【译注 1】,通常也包括你设计C++函数库的方式。例如,你会看到更多参数和返回值类型为智能指针(smart pointer),同时也会看到函数通过值传递返回大型对象。你将会发现在大多数的代码示例中充斥着新特性的身影。例如,几乎每5行现代C++代码示例都会使用到auto。
(2)C++11的其他特性也很棒。但是请先熟悉下面这些,正是由于这些特性的广泛使用使得C++11代码如同其他现代主流语言一样整洁、安全和高效,与此同时保持了C++传统的性能优势。
提示:
本文只做概要总结而不是详尽基本原理和优缺点分析。详细分析请参见其他文章。
译注:
1. Programming idiom:编程习语,在一种或多种编程语言中重复出现的表达形式,用来表示没有在编程语言中内置的简单的任务或者算法,也可以用来表示在编程语言中内置的不常用或者不典型的某个特性。编程习语也可以在更广泛的范围内使用,比如代指复杂的算法或者设计模式。
auto
基于以下两个原因,尽可能使用auto:首先,使用auto会避免重复声明编译器已经知道的类型。
// C++98 map<int,string>::iterator i = m.begin(); // C++11 auto i = begin(m);
其次,当使用未知类型或者类型名称不易理解时使用auto会更加便利,例如大多数的lambda函数【译注 2】——你甚至不能简单的拼写出类型的名字。
// C++98 binder2nd< greater<int> > x = bind2nd( greater<int>(), 42 ); // C++11 auto x = [](int i) { return i > 42; };
需要注意,使用auto并不改变代码的含义。代码仍然是静态类型【译注 3】,每个表达式的类型都是清晰和明确的;C++11只是不需要我们重复声明类型名。一些人刚开始可能会害怕在这里使用auto,因为感觉好像没有(重复)声明我们需要的类型就意味着可能会碰巧得到一个不同的类型。如果你想要明确地进行一次强制类型转换,没有问题,声明目标类型就好了。然而大多数情况下,只要使用auto就可以了;几乎不会出现错误地拿到一个不同类型的情况,即便出现错误,C++的强静态类型系统也会由编译器让你知道这个错误,因为你正试图访问一个变量没有的成员函数或是错误地调用了该函数。
译注:
2. lambda function (λ函数):编程语言支持λ函数/λ表达式可以使得代码更易于理解,同时也可以使得代码变得更简洁,关于λ函数的技术解释可以参考,维基百科labmda calculus,也可以从《从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势》博客中得到直观的解释。
3. 动态类型语言(dynamic typing language)是指类型检查发生在运行期间(run-time)的语言。静态类型语言(static typing language)是类型检查发生在编译期间(compile-time)的语言。
智能指针:无须delete
请始终使用标准智能指针以及非占有原始指针(non-owning raw pointer)。绝不要使用占有原生指针(owning raw pointer)和delete操作,除非你是在实现自己的底层数据结构这种少见的情况下(即使在此时也需要在class范围内保持完好的封装)。如果只能够知道你是另一个对象唯一的所有者,请使用unique_ptr来表示唯一所有权。一个“new T”表达式会马上初始化另一个引用它的对象,通常是一个unique_ptr。
// C++11 Pimpl Idiom class widget { widget(); ~widget(); private: class impl; unique_ptr<impl> pimpl; }; // in .cpp file class impl { ::: }; widget::widget() : pimpl( new impl() ) { } widget::~widget() = default;
使用shared_ptr来表示共享所有权。推荐使用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; };
如果你知道另一个对象存在时间会更长久并且希望跟踪它,使用一个非占有 (non-owning)原始指针。
// C++11 class node { vector< unique_ptr<node> > children; node* parent; public: ::: };
nullptr
始终使用nullptr表示一个null指针值,绝不要使用数字0或者NULL宏,因为它们也可以代表一个整数或者指针从而产生歧义。
// C++98 int* p = 0; // C++11 int* p = nullptr;
Range for
基于范围的循环使得按顺序访问其中的每个元素变得非常方便。
// C++98 for( vector<double>::iterator i = v.begin(); i != v.end(); ++i ) { total += *i; } // C++11 for( auto d : v ) { total += d; }
非成员(nonmember) begin和end
始终使用非成员begin和end,因为它是可扩展的并且可以应用在所有的容器类型(container type),不仅仅是遵循了STL风格提供了.begin()和.end()成员函数的容器,甚至数组都可以使用。
如果你使用了一个非STL风格的collection类型,虽然提供了迭代但没有提供STL的.begin()和.end(),通常可以为这个类型编写自己的非成员begin和end来进行重载。这样你就可以使用STL容器的编程风格来遍历该类型。C++11标准提供了示例数组就是这样