1. 程式人生 > >優秀部落格翻譯-現代C++11程式碼風格

優秀部落格翻譯-現代C++11程式碼風格

部落格原文:

開頭:

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 ...
});

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:增加了統一初始化