博客原文:
开头:
C++11 就像一门全新语言 – C++之父 Bjarne Stroustrup
C++11 标准提供了很多有用的特性。本文重点介绍一些有用的特性和那些相比于C++98,使C++ 11看上去时就像一门全新的语言的特性,因为:
- 新特性会改变你写C++代码的风格和习惯用法,甚至影响你未来的设计C++库风格。例如,你会看到更多的智能指针和返回类型,返回大对象的函数
- 这些特性被广泛地被应用在其他高级语言中。比如,在现代C++代码中每5行之内你会看到到处都是”auto”
使用其他C++ 11高级特性一样。但是先尝试使用这些特性,因为很明显你能感觉到为什么C++ 11 代码可以如此整洁、安全、高效,正如其他主流高级语言一样。
和C++传统一样强大。
注意:
- 本文只是做一个简短的总结说明,不会试图提供详细的解释和程序代码分析,那些会在其它文章中来讲;
- 这是一篇实时更新的文章,在文章的最后会显示每次的变更记录;
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
关键字非常方便,例如大多数的lanbda函数表达式类型,那些很难拼写或者根本不知道类型的。
// C++98
binder2nd< greater > x = bind2nd( greater(), 42 );
// C++11
auto x = [](int i) { return i > 42; };
注意,使用auto
关键字不会改变代码本身含义。类型本身还是静态的,每一个表达式也明确是清晰的,新语法使我们不用再重复书写类型名称。
有一些人原来害怕使用auto
,因为感觉不声明为强制类型在某些情况下会得到另一种不同的类型。如果你想明确强转成另一种类型,这个时候声明为你想获得的目标类型就行。
然而大多数情况,都放心使用auto
,几乎不可能出现获取到另一种类型的情况。对于这些强类型语言,会有编译器提示你。
智能指针:无需担心delete
使用标准库的智能指针和弱指针。避免使用原始的指针和delete操作,除了极端情况如自己实现底层数据结构。
如果是一个对象的唯一所有者,使用unique_ptr
来标识唯一所有权。一个”new T”表达式应该立刻初始化另一个拥有它的对象,典型的一个unique_ptr
使用场景:
一个经典的例子是 the 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
标识共享指针。推荐使用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;
};
如果你字段一个对象会一直存在,你想要观察它,那么使用原始指针。
nullptr
空指针一律使用nullptr,绝不使用整型0和NULL,因为会引起误解以为空指针是整型或指针。
// C++98
int* p = 0;
// C++11
int* p = nullptr;
范围循环
使用范围循环相当方便:
// C++98
for( vector<int>::iterator i = v.begin(); i != v.end(); ++i ) {
total += *i;
}
// C++11
for( auto d : v ) {
total += d;
}
非成员函数的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(x)和end(x)重载函数,那样可以和STL容器保持一致的代码风格。C数组也是这样的一种类型,标准库提供数组的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) );
lambda函数和算法
lambdas是一个游戏规则,未来将会极大地改变你编码风格,提高代码的优雅度和高效率。lambdas会使现存的STL算法提高大概100x倍。最新C、++代码库都在广泛使用lambdas(比如PPL),甚至有一些库已经要求必须使用lambdas(比如C++ AMP)
这里有一个快速理解lambadas函数的例子:
找出数组中第一个>x并
// 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; } );
想要一个循环或相似的语言特性。不用担心,只需要写成一个模板函数。有了lambdas表达式你可以像一个语言特性那样方便地使用,同时伸展性也很好,因为它就是一个库而不是硬件支撑的语言特性。
// 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 ...
});
真的可以在很多场合使用lambdas,不仅仅是在C++,而且已经被广泛应用在多门主流语言中。一个好的lambdas入门可以看我的这篇文章:无处不在的lambdas(https://herbsutter.com/2010/10/30/pdc-languages-panel-andshortened-lambdas-talk/)
Move / &&
Move是优化拷贝最好的思想,虽然它还会增强其他特性比如呈现forwarding。
Move语义改变了我们设计API的思路。经常我们会这样设计API的返回值:
// 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
但你想要获得比复制更高效率的场景时,类型尽可能使用move语义。
统一初始化和初始化列表
未改变的:当初始化一个非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
在其他情况下,创建一个对象时,使用{}大括号代替使用()。使用大括号可以避免几个潜在问题:不会偶然得到一个转换(比如float转int),也可以避免得到C++98…
// 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} { /*...*/ }
最后,有时也相当方便可以不用创建类型匹配的函数参数的临时变量:
void draw_rect( rectangle );
// C++98
draw_rect( rectangle( myobj.origin, selection.extents ) );
// C++11
draw_rect( { myobj.origin, selection.extents } );
唯一一个地方我不建议使用大括号的是简单类型的非POD变量的初始化,比如auto=begin(v)
,会使代码难看丑陋,因为这是一个class类型,所以我知道我不必担心非法转换,现代编译器已经可以优化额外的拷贝。(额外的右值,如果是右值类型)
更多
这里有更多关于现代C++11介绍(http://www2.research.att.com/~bs/C++0xFAQ.html),同时在未来我计划更深入地写的其中的某些部分和其他我们知道和感兴趣的C++11特性。
但就现在,上面介绍的都是必须知道的特性。这些特性奠定了现代C++设计风格,使C++代码看上去和呈现的它应有的方式,你会普遍地看到和写到其中的某一些特性,正如你看到和写的那样,这些特性使C++代码整洁、安全、高效,会在行业中继续保持和依赖相当多年。
主要修改历史
- 2011-10-30 增加了lambdas中C# 锁的例子,重新排序了智能指针部分,把unique_ptr放最前面
- 2011-11-01:增加了统一初始化