C++開(kāi)發(fā)者都應(yīng)該使用的10個(gè)C++11特性
在C++11新標(biāo)準(zhǔn)中,語(yǔ)言本身和標(biāo)準(zhǔn)庫(kù)都增加了很多新內(nèi)容,本文只涉及了一些皮毛。不過(guò)我相信這些新特性當(dāng)中有一些,應(yīng)該成為所有C++開(kāi)發(fā)者的 常規(guī)裝備。你也許看到過(guò)許多類(lèi)似介紹各種C++11特性的文章。下面是我總結(jié)的,C++開(kāi)發(fā)者都需要學(xué)習(xí)和使用的C++11新特性。
auto
在C++11之前,auto關(guān)鍵字用來(lái)指定存儲(chǔ)期。在新標(biāo)準(zhǔn)中,它的功能變?yōu)轭?lèi)型推斷。auto現(xiàn)在成了一個(gè)類(lèi)型的占位符,通知編譯器去根據(jù)初始化 代碼推斷所聲明變量的真實(shí)類(lèi)型。各種作用域內(nèi)聲明變量都可以用到它。例如,名空間中,程序塊中,或是for循環(huán)的初始化語(yǔ)句中。
- auto i = 42; // i is an int
 - auto l = 42LL; // l is an long long
 - auto p = new foo(); // p is a foo*
 
使用auto通常意味著更短的代碼(除非你所用類(lèi)型是int,它會(huì)比auto少一個(gè)字母)。試想一下當(dāng)你遍歷STL容器時(shí)需要聲明的那些迭代器(iterator)。現(xiàn)在不需要去聲明那些typedef就可以得到簡(jiǎn)潔的代碼了。
- std::map<std::string, std::vector<int>> map;
 - for(auto it = begin(map); it != end(map); ++it)
 - {
 - }
 
需要注意的是,auto不能用來(lái)聲明函數(shù)的返回值。但如果函數(shù)有一個(gè)尾隨的返回類(lèi)型時(shí),auto是可以出現(xiàn)在函數(shù)聲明中返回值位置。這種情況下,auto 并不是告訴編譯器去推斷返回類(lèi)型,而是指引編譯器去函數(shù)的末端尋找返回值類(lèi)型。在下面這個(gè)例子中,函數(shù)的返回值類(lèi)型就是operator+操作符作用在 T1、T2類(lèi)型變量上的返回值類(lèi)型。
- template <typename T1, typename T2>
 - auto compose(T1 t1, T2 t2) -> decltype(t1 + t2)
 - {
 - return t1+t2;
 - }
 - auto v = compose(2, 3.14); // v's type is double
 
nullptr
以前都是用0來(lái)表示空指針的,但由于0可以被隱式類(lèi)型轉(zhuǎn)換為整形,這就會(huì)存在一些問(wèn)題。關(guān)鍵字nullptr是std::nullptr_t類(lèi)型的 值,用來(lái)指代空指針。nullptr和任何指針類(lèi)型以及類(lèi)成員指針類(lèi)型的空值之間可以發(fā)生隱式類(lèi)型轉(zhuǎn)換,同樣也可以隱式轉(zhuǎn)換為bool型(取值為 false)。但是不存在到整形的隱式類(lèi)型轉(zhuǎn)換。
- void foo(int* p) {}
 - void bar(std::shared_ptr<int> p) {}
 - int* p1 = NULL;
 - int* p2 = nullptr;
 - if(p1 == p2)
 - {
 - }
 - foo(nullptr);
 - bar(nullptr);
 - bool f = nullptr;
 - int i = nullptr; // error: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
 
為了向前兼容,0仍然是個(gè)合法的空指針值。
Range-based for loops (基于范圍的for循環(huán))
為了在遍歷容器時(shí)支持”foreach”用法,C++11擴(kuò)展了for語(yǔ)句的語(yǔ)法。用這個(gè)新的寫(xiě)法,可以遍歷C類(lèi)型的數(shù)組、初始化列表以及任何重載了非成員的begin()和end()函數(shù)的類(lèi)型。
如果你只是想對(duì)集合或數(shù)組的每個(gè)元素做一些操作,而不關(guān)心下標(biāo)、迭代器位置或者元素個(gè)數(shù),那么這種foreach的for循環(huán)將會(huì)非常有用。
- std::map<std::string, std::vector<int>> map;
 - std::vector<int> v;
 - v.push_back(1);
 - v.push_back(2);
 - v.push_back(3);
 - map["one"] = v;
 - for(const auto& kvp : map)
 - {
 - std::cout << kvp.first << std::endl;
 - for(auto v : kvp.second)
 - {
 - std::cout << v << std::endl;
 - }
 - }
 - int arr[] = {1,2,3,4,5};
 - for(int& e : arr)
 - {
 - e = e*e;
 - }
 - C++ 11 CPP 11 features
 

#p#
Override和final
我總覺(jué)得 C++中虛函數(shù)的設(shè)計(jì)很差勁,因?yàn)闀r(shí)至今日仍然沒(méi)有一個(gè)強(qiáng)制的機(jī)制來(lái)標(biāo)識(shí)虛函數(shù)會(huì)在派生類(lèi)里被改寫(xiě)。vitual關(guān)鍵字是可選的,這使得閱讀代碼變得很費(fèi)勁。因?yàn)榭赡苄枰匪莸嚼^承體系的源頭才能確定某個(gè)方法是否是虛函數(shù)。為了增加可讀性,我總是在派生類(lèi)里也寫(xiě)上virtual關(guān)鍵字,并且也鼓勵(lì)大家都這么做。即使這樣,仍然會(huì)產(chǎn)生一些微妙的錯(cuò)誤。看下面這個(gè)例子:
- class B
 - {
 - public:
 - virtual void f(short) {std::cout << "B::f" << std::endl;}
 - };
 - class D : public B
 - {
 - public:
 - virtual void f(int) {std::cout << "D::f" << std::endl;}
 - };
 
D::f 按理應(yīng)當(dāng)重寫(xiě) B::f。然而二者的聲明是不同的,一個(gè)參數(shù)是short,另一個(gè)是int。因此D::f(原文為B::f,可能是作者筆誤——譯者注)只是擁有同樣名字 的另一個(gè)函數(shù)(重載)而不是重寫(xiě)。當(dāng)你通過(guò)B類(lèi)型的指針調(diào)用f()可能會(huì)期望打印出D::f,但實(shí)際上則會(huì)打出 B::f 。
另一個(gè)很微妙的錯(cuò)誤情況:參數(shù)相同,但是基類(lèi)的函數(shù)是const的,派生類(lèi)的函數(shù)卻不是。
- class B
 - {
 - public:
 - virtual void f(int) const {std::cout << "B::f " << std::endl;}
 - };
 - class D : public B
 - {
 - public:
 - virtual void f(int) {std::cout << "D::f" << std::endl;}
 - };
 
同樣,這兩個(gè)函數(shù)是重載而不是重寫(xiě),所以你通過(guò)B類(lèi)型指針調(diào)用f()將打印B::f,而不是D::f。
幸運(yùn)的是,現(xiàn)在有一種方式能描述你的意圖。新標(biāo)準(zhǔn)加入了兩個(gè)新的標(biāo)識(shí)符(不是關(guān)鍵字)::
- override,表示函數(shù)應(yīng)當(dāng)重寫(xiě)基類(lèi)中的虛函數(shù)。
 - final,表示派生類(lèi)不應(yīng)當(dāng)重寫(xiě)這個(gè)虛函數(shù)。
 
第一個(gè)的例子如下:
- class B
 - {
 - public:
 - virtual void f(short) {std::cout << "B::f" << std::endl;}
 - };
 - class D : public B
 - {
 - public:
 - virtual void f(int) override {std::cout << "D::f" << std::endl;}
 - };
 
現(xiàn)在這將觸發(fā)一個(gè)編譯錯(cuò)誤(后面那個(gè)例子,如果也寫(xiě)上override標(biāo)識(shí),會(huì)得到相同的錯(cuò)誤提示):
- 'D::f' : method with override specifier 'override' did not override any base class methods
 
另一方面,如果你希望函數(shù)不要再被派生類(lèi)進(jìn)一步重寫(xiě),你可以把它標(biāo)識(shí)為final??梢栽诨?lèi)或任何派生類(lèi)中使用final。在派生類(lèi)中,可以同時(shí)使用override和final標(biāo)識(shí)。
- class B
 - {
 - public:
 - virtual void f(int) {std::cout << "B::f" << std::endl;}
 - };
 - class D : public B
 - {
 - public:
 - virtual void f(int) override final {std::cout << "D::f" << std::endl;}
 - };
 - class F : public D
 - {
 - public:
 - virtual void f(int) override {std::cout << "F::f" << std::endl;}
 - };
 
被標(biāo)記成final的函數(shù)將不能再被F::f重寫(xiě)。
Strongly-typed enums 強(qiáng)類(lèi)型枚舉
傳統(tǒng)的C++枚舉類(lèi)型存在一些缺陷:它們會(huì)將枚舉常量暴露在外層作用域中(這可能導(dǎo)致名字沖突,如果同一個(gè)作用域中存在兩個(gè)不同的枚舉類(lèi)型,但是具有相同的枚舉常量就會(huì)沖突),而且它們會(huì)被隱式轉(zhuǎn)換為整形,無(wú)法擁有特定的用戶(hù)定義類(lèi)型。
在C++11中通過(guò)引入了一個(gè)稱(chēng)為強(qiáng)類(lèi)型枚舉的新類(lèi)型,修正了這種情況。強(qiáng)類(lèi)型枚舉由關(guān)鍵字enum class標(biāo)識(shí)。它不會(huì)將枚舉常量暴露到外層作用域中,也不會(huì)隱式轉(zhuǎn)換為整形,并且擁有用戶(hù)指定的特定類(lèi)型(傳統(tǒng)枚舉也增加了這個(gè)性質(zhì))。
- enum class Options {None, One, All};
 - Options o = Options::All;
 
#p#
Smart Pointers 智能指針
已經(jīng)有成千上萬(wàn)的文章討論這個(gè)問(wèn)題了,所以我只想說(shuō):現(xiàn)在能使用的,帶引用計(jì)數(shù),并且能自動(dòng)釋放內(nèi)存的智能指針包括以下幾種:
- unique_ptr: 如果內(nèi)存資源的所有權(quán)不需要共享,就應(yīng)當(dāng)使用這個(gè)(它沒(méi)有拷貝構(gòu)造函數(shù)),但是它可以轉(zhuǎn)讓給另一個(gè)unique_ptr(存在move構(gòu)造函數(shù))。
 - shared_ptr: 如果內(nèi)存資源需要共享,那么使用這個(gè)(所以叫這個(gè)名字)。
 - weak_ptr: 持有被shared_ptr所管理對(duì)象的引用,但是不會(huì)改變引用計(jì)數(shù)值。它被用來(lái)打破依賴(lài)循環(huán)(想象在一個(gè)tree結(jié)構(gòu)中,父節(jié)點(diǎn)通過(guò)一個(gè)共享所有權(quán)的引 用(chared_ptr)引用子節(jié)點(diǎn),同時(shí)子節(jié)點(diǎn)又必須持有父節(jié)點(diǎn)的引用。如果這第二個(gè)引用也共享所有權(quán),就會(huì)導(dǎo)致一個(gè)循環(huán),最終兩個(gè)節(jié)點(diǎn)內(nèi)存都無(wú)法釋 放)。
 
另一方面,auto_ptr已經(jīng)被廢棄,不會(huì)再使用了。
什么時(shí)候使用unique_ptr,什么時(shí)候使用shared_ptr取決于對(duì)所有權(quán)的需求,我建議閱讀以下的討 論:http://stackoverflow.com/questions/15648844/using-smart-pointers-for- class-members
以下第一個(gè)例子使用了unique_ptr。如果你想把對(duì)象所有權(quán)轉(zhuǎn)移給另一個(gè)unique_ptr,需要使用std::move(我會(huì)在最后幾段討論這個(gè)函數(shù))。在所有權(quán)轉(zhuǎn)移后,交出所有權(quán)的智能指針將為空,get()函數(shù)將返回nullptr。
- void foo(int* p)
 - {
 - std::cout << *p << std::endl;
 - }
 - std::unique_ptr<int> p1(new int(42));
 - std::unique_ptr<int> p2 = std::move(p1); // transfer ownership
 - if(p1)
 - foo(p1.get());
 - (*p2)++;
 - if(p2)
 - foo(p2.get());
 
第二個(gè)例子展示了shared_ptr。用法相似,但語(yǔ)義不同,此時(shí)所有權(quán)是共享的。
- void foo(int* p)
 - {
 - }
 - void bar(std::shared_ptr<int> p)
 - {
 - ++(*p);
 - }
 - std::shared_ptr<int> p1(new int(42));
 - std::shared_ptr<int> p2 = p1;
 - bar(p1);
 - foo(p2.get());
 
第一個(gè)聲明和以下這行是等價(jià)的:
- auto p3 = std::make_shared<int>(42);
 
make_shared<T>是一個(gè)非成員函數(shù),使用它的好處是可以一次性分配共享對(duì)象和智能指針自身的內(nèi)存。而顯示地使用 shared_ptr構(gòu)造函數(shù)來(lái)構(gòu)造則至少需要兩次內(nèi)存分配。除了會(huì)產(chǎn)生額外的開(kāi)銷(xiāo),還可能會(huì)導(dǎo)致內(nèi)存泄漏。在下面這個(gè)例子中,如果seed()拋出一個(gè) 錯(cuò)誤就會(huì)產(chǎn)生內(nèi)存泄漏。
- void foo(std::shared_ptr<int> p, int init)
 - {
 - *p = init;
 - }
 - foo(std::shared_ptr<int>(new int(42)), seed());
 
如果使用make_shared就不會(huì)有這個(gè)問(wèn)題了。第三個(gè)例子展示了weak_ptr。注意,你必須調(diào)用lock()來(lái)獲得被引用對(duì)象的shared_ptr,通過(guò)它才能訪問(wèn)這個(gè)對(duì)象。
- auto p = std::make_shared<int>(42);
 - std::weak_ptr<int> wp = p;
 - {
 - auto sp = wp.lock();
 - std::cout << *sp << std::endl;
 - }
 - p.reset();
 - if(wp.expired())
 - std::cout << "expired" << std::endl;
 
如果你試圖鎖定(lock)一個(gè)過(guò)期(指被弱引用對(duì)象已經(jīng)被釋放)的weak_ptr,那你將獲得一個(gè)空的shared_ptr.
Lambdas
匿名函數(shù)(也叫l(wèi)ambda)已經(jīng)加入到C++中,并很快異軍突起。這個(gè)從函數(shù)式編程中借來(lái)的強(qiáng)大特性,使很多其他特性以及類(lèi)庫(kù)得以實(shí)現(xiàn)。你可以在 任何使用函數(shù)對(duì)象或者函子(functor)或std::function的地方使用lambda。你可以從這里 (http://msdn.microsoft.com/en-us/library/dd293603.aspx)找到語(yǔ)法說(shuō)明。
- std::vector<int> v;
 - v.push_back(1);
 - v.push_back(2);
 - v.push_back(3);
 - std::for_each(std::begin(v), std::end(v), [](int n) {std::cout << n << std::endl;});
 - auto is_odd = [](int n) {return n%2==1;};
 - auto pos = std::find_if(std::begin(v), std::end(v), is_odd);
 - if(pos != std::end(v))
 - std::cout << *pos << std::endl;
 
#p#
更復(fù)雜的是遞歸lambda??紤]一個(gè)實(shí)現(xiàn)Fibonacci函數(shù)的lambda。如果你試圖用auto來(lái)聲明,就會(huì)得到一個(gè)編譯錯(cuò)誤。
- auto fib = [&fib](int n) {return n < 2 ? 1 : fib(n-1) + fib(n-2);};
 
- error C3533: 'auto &': a parameter cannot have a type that contains 'auto'
 - error C3531: 'fib': a symbol whose type contains 'auto' must have an initializer
 - error C3536: 'fib': cannot be used before it is initialized
 - error C2064: term does not evaluate to a function taking 1 arguments
 
問(wèn)題出在auto意味著對(duì)象類(lèi)型由初始表達(dá)式?jīng)Q定,然而初始表達(dá)式又包含了對(duì)其自身的引用,因此要求先知道它的類(lèi)型,這就導(dǎo)致了無(wú)窮遞歸。解決問(wèn)題的關(guān)鍵就是打破這種循環(huán)依賴(lài),用std::function顯式的指定函數(shù)類(lèi)型:
- std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);};
 
非成員begin()和end()
也許你注意到了,我在前面的例子中已經(jīng)用到了非成員begin()和end()函數(shù)。他們是新加入標(biāo)準(zhǔn)庫(kù)的,除了能提高了代碼一致性,還有助于更多 地使用泛型編程。它們和所有的STL容器兼容。更重要的是,他們是可重載的。所以它們可以被擴(kuò)展到支持任何類(lèi)型。對(duì)C類(lèi)型數(shù)組的重載已經(jīng)包含在標(biāo)準(zhǔn)庫(kù)中 了。
我們還用上一個(gè)例子中的代碼來(lái)說(shuō)明,在這個(gè)例子中我打印了一個(gè)數(shù)組然后查找它的第一個(gè)偶數(shù)元素。如果std::vector被替換成C類(lèi)型數(shù)組。代碼可能看起來(lái)是這樣的:
- int arr[] = {1,2,3};
 - std::for_each(&arr[0], &arr[0]+sizeof(arr)/sizeof(arr[0]), [](int n) {std::cout << n << std::endl;});
 - auto is_odd = [](int n) {return n%2==1;};
 - auto begin = &arr[0];
 - auto end = &arr[0]+sizeof(arr)/sizeof(arr[0]);
 - auto pos = std::find_if(begin, end, is_odd);
 - if(pos != end)
 - std::cout << *pos << std::endl;
 
如果使用非成員的begin()和end()來(lái)實(shí)現(xiàn),就會(huì)是以下這樣的:
- int arr[] = {1,2,3};
 - std::for_each(std::begin(arr), std::end(arr), [](int n) {std::cout << n << std::endl;});
 - auto is_odd = [](int n) {return n%2==1;};
 - auto pos = std::find_if(std::begin(arr), std::end(arr), is_odd);
 - if(pos != std::end(arr))
 - std::cout << *pos << std::endl;
 
這基本上和使用std::vecto的代碼是完全一樣的。這就意味著我們可以寫(xiě)一個(gè)泛型函數(shù)處理所有支持begin()和end()的類(lèi)型。
- template <typename Iterator>
 - void bar(Iterator begin, Iterator end)
 - {
 - std::for_each(begin, end, [](int n) {std::cout << n << std::endl;});
 - auto is_odd = [](int n) {return n%2==1;};
 - auto pos = std::find_if(begin, end, is_odd);
 - if(pos != end)
 - std::cout << *pos << std::endl;
 - }
 - template <typename C>
 - void foo(C c)
 - {
 - bar(std::begin(c), std::end(c));
 - }
 - template <typename T, size_t N>
 - void foo(T(&arr)[N])
 - {
 - bar(std::begin(arr), std::end(arr));
 - }
 - int arr[] = {1,2,3};
 - foo(arr);
 - std::vector<int> v;
 - v.push_back(1);
 - v.push_back(2);
 - v.push_back(3);
 - foo(v);
 
static_assert和 type traits
static_assert提供一個(gè)編譯時(shí)的斷言檢查。如果斷言為真,什么也不會(huì)發(fā)生。如果斷言為假,編譯器會(huì)打印一個(gè)特殊的錯(cuò)誤信息。
- template <typename T, size_t Size>
 - class Vector
 - {
 - static_assert(Size < 3, "Size is too small");
 - T _points[Size];
 - };
 - int main()
 - {
 - Vector<int, 16> a1;
 - Vector<double, 2> a2;
 - return 0;
 - }
 
- error C2338: Size is too small
 - see reference to class template instantiation 'Vector<T,Size>' being compiled
 - with
 - [
 - T=double,
 - Size=2
 - ]
 
#p#
static_assert和type traits一起使用能發(fā)揮更大的威力。type traits是一些class,在編譯時(shí)提供關(guān)于類(lèi)型的信息。在頭文件<type_traits>中可以找到它們。這個(gè)頭文件中有好幾種 class: helper class,用來(lái)產(chǎn)生編譯時(shí)常量。type traits class,用來(lái)在編譯時(shí)獲取類(lèi)型信息,還有就是type transformation class,他們可以將已存在的類(lèi)型變換為新的類(lèi)型。
下面這段代碼原本期望只做用于整數(shù)類(lèi)型。
- template <typename T1, typename T2>
 - auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
 - {
 - return t1 + t2;
 - }
 
但是如果有人寫(xiě)出如下代碼,編譯器并不會(huì)報(bào)錯(cuò)
- std::cout << add(1, 3.14) << std::endl;
 - std::cout << add("one", 2) << std::endl;
 
程序會(huì)打印出4.14和”e”。但是如果我們加上編譯時(shí)斷言,那么以上兩行將產(chǎn)生編譯錯(cuò)誤。
- template <typename T1, typename T2>
 - auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
 - {
 - static_assert(std::is_integral<T1>::value, "Type T1 must be integral");
 - static_assert(std::is_integral<T2>::value, "Type T2 must be integral");
 - return t1 + t2;
 - }
 
- error C2338: Type T2 must be integral
 - see reference to function template instantiation 'T2 add<int,double>(T1,T2)' being compiled
 - with
 - [
 - T2=double,
 - T1=int
 - ]
 - error C2338: Type T1 must be integral
 - see reference to function template instantiation 'T1 add<const char*,int>(T1,T2)' being compiled
 - with
 - [
 - T1=const char *,
 - T2=int
 - ]
 
Move semantics (Move語(yǔ)義)
這是C++11中所涵蓋的另一個(gè)重要話題。就這個(gè)話題可以寫(xiě)出一系列文章,僅用一個(gè)段落來(lái)說(shuō)明顯然是不夠的。因此在這里我不會(huì)過(guò)多的深入細(xì)節(jié),如果你還不是很熟悉這個(gè)話題,我鼓勵(lì)你去閱讀更多地資料。
C++11加入了右值引用(rvalue reference)的概念(用&&標(biāo)識(shí)),用來(lái)區(qū)分對(duì)左值和右值的引用。左值就是一個(gè)有名字的對(duì)象,而右值則是一個(gè)無(wú)名對(duì)象(臨時(shí)對(duì) 象)。move語(yǔ)義允許修改右值(以前右值被看作是不可修改的,等同于const T&類(lèi)型)。
C++的class或者struct以前都有一些隱含的成員函數(shù):默認(rèn)構(gòu)造函數(shù)(僅當(dāng)沒(méi)有顯示定義任何其他構(gòu)造函數(shù)時(shí)才存在),拷貝構(gòu)造函數(shù),析構(gòu) 函數(shù)還有拷貝賦值操作符??截悩?gòu)造函數(shù)和拷貝賦值操作符提供bit-wise的拷貝(淺拷貝),也就是逐個(gè)bit拷貝對(duì)象。也就是說(shuō),如果你有一個(gè)類(lèi)包含 指向其他對(duì)象的指針,拷貝時(shí)只會(huì)拷貝指針的值而不會(huì)管指向的對(duì)象。在某些情況下這種做法是沒(méi)問(wèn)題的,但在很多情況下,實(shí)際上你需要的是深拷貝,也就是說(shuō)你 希望拷貝指針?biāo)赶虻膶?duì)象。而不是拷貝指針的值。這種情況下,你需要顯示地提供拷貝構(gòu)造函數(shù)與拷貝賦值操作符來(lái)進(jìn)行深拷貝。
如果你用來(lái)初始化或拷貝的源對(duì)象是個(gè)右值(臨時(shí)對(duì)象)會(huì)怎么樣呢?你仍然需要拷貝它的值,但隨后很快右值就會(huì)被釋放。這意味著產(chǎn)生了額外的操作開(kāi)銷(xiāo),包括原本并不需要的空間分配以及內(nèi)存拷貝。
現(xiàn)在說(shuō)說(shuō)move constructor和move assignment operator。這兩個(gè)函數(shù)接收T&&類(lèi)型的參數(shù),也就是一個(gè)右值。在這種情況下,它們可以修改右值對(duì)象,例如“偷走”它們內(nèi)部指針?biāo)?指向的對(duì)象。舉個(gè)例子,一個(gè)容器的實(shí)現(xiàn)(例如vector或者queue)可能包含一個(gè)指向元素?cái)?shù)組的指針。當(dāng)用一個(gè)臨時(shí)對(duì)象初始化一個(gè)對(duì)象時(shí),我們不需 要分配另一個(gè)數(shù)組,從臨時(shí)對(duì)象中把值復(fù)制過(guò)來(lái),然后在臨時(shí)對(duì)象析構(gòu)時(shí)釋放它的內(nèi)存。我們只需要將指向數(shù)組內(nèi)存的指針值復(fù)制過(guò)來(lái),由此節(jié)約了一次內(nèi)存分配, 一次元數(shù)組的復(fù)制以及后來(lái)的內(nèi)存釋放。
以下代碼實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的buffer。這個(gè)buffer有一個(gè)成員記錄buffer名稱(chēng)(為了便于以下的說(shuō)明),一個(gè)指針(封裝在unique_ptr中)指向元素為T(mén)類(lèi)型的數(shù)組,還有一個(gè)記錄數(shù)組長(zhǎng)度的變量。
#p#
- template <typename T>
 - class Buffer
 - {
 - std::string _name;
 - size_t _size;
 - std::unique_ptr<T[]> _buffer;
 - public:
 - // default constructor
 - Buffer():
 - _size(16),
 - _buffer(new T[16])
 - {}
 - // constructor
 - Buffer(const std::string& name, size_t size):
 - _name(name),
 - _size(size),
 - _buffer(new T[size])
 - {}
 - // copy constructor
 - Buffer(const Buffer& copy):
 - _name(copy._name),
 - _size(copy._size),
 - _buffer(new T[copy._size])
 - {
 - T* source = copy._buffer.get();
 - T* dest = _buffer.get();
 - std::copy(source, source + copy._size, dest);
 - }
 - // copy assignment operator
 - Buffer& operator=(const Buffer& copy)
 - {
 - if(this != ©)
 - {
 - _name = copy._name;
 - if(_size != copy._size)
 - {
 - _buffer = nullptr;
 - _size = copy._size;
 - _buffer = _size > 0 > new T[_size] : nullptr;
 - }
 - T* source = copy._buffer.get();
 - T* dest = _buffer.get();
 - std::copy(source, source + copy._size, dest);
 - }
 - return *this;
 - }
 - // move constructor
 - Buffer(Buffer&& temp):
 - _name(std::move(temp._name)),
 - _size(temp._size),
 - _buffer(std::move(temp._buffer))
 - {
 - temp._buffer = nullptr;
 - temp._size = 0;
 - }
 - // move assignment operator
 - Buffer& operator=(Buffer&& temp)
 - {
 - assert(this != &temp); // assert if this is not a temporary
 - _buffer = nullptr;
 - _size = temp._size;
 - _buffer = std::move(temp._buffer);
 - _name = std::move(temp._name);
 - temp._buffer = nullptr;
 - temp._size = 0;
 - return *this;
 - }
 - };
 - template <typename T>
 - Buffer<T> getBuffer(const std::string& name)
 - {
 - Buffer<T> b(name, 128);
 - return b;
 - }
 - int main()
 - {
 - Buffer<int> b1;
 - Buffer<int> b2("buf2", 64);
 - Buffer<int> b3 = b2;
 - Buffer<int> b4 = getBuffer<int>("buf4");
 - b1 = getBuffer<int>("buf5");
 - return 0;
 - }
 
默認(rèn)的copy constructor以及copy assignment operator大家應(yīng)該很熟悉了。C++11中新增的是move constructor以及move assignment operator,這兩個(gè)函數(shù)根據(jù)上文所描述的move語(yǔ)義實(shí)現(xiàn)。如果你運(yùn)行這段代碼,你就會(huì)發(fā)現(xiàn)b4構(gòu)造時(shí),move constructor會(huì)被調(diào)用。同樣,對(duì)b1賦值時(shí),move assignment operator會(huì)被調(diào)用。原因就在于getBuffer()的返回值是一個(gè)臨時(shí)對(duì)象——也就是右值。
你也許注意到了,move constuctor中當(dāng)我們初始化變量name和指向buffer的指針時(shí),我們使用了std::move。name實(shí)際上是一個(gè) string,std::string實(shí)現(xiàn)了move語(yǔ)義。std::unique_ptr也一樣。但是如果我們寫(xiě)_name(temp._name), 那么copy constructor將會(huì)被調(diào)用。不過(guò)對(duì)于_buffer來(lái)說(shuō)不能這么寫(xiě),因?yàn)閟td::unique_ptr沒(méi)有copy constructor。但為什么std::string的move constructor此時(shí)沒(méi)有被調(diào)到呢?這是因?yàn)殡m然我們使用一個(gè)右值調(diào)用了Buffer的move constructor,但在這個(gè)構(gòu)造函數(shù)內(nèi),它實(shí)際上是個(gè)左值。為什么?因?yàn)樗怯忻值?mdash;—“temp”。一個(gè)有名字的對(duì)象就是左值。為了再把它變?yōu)?右值(以便調(diào)用move constructor)必須使用std::move。這個(gè)函數(shù)僅僅是把一個(gè)左值引用變?yōu)橐粋€(gè)右值引用。
更新:雖然這個(gè)例子是為了說(shuō)明如何實(shí)現(xiàn)move constructor以及move assignment operator,但具體的實(shí)現(xiàn)方式并不是唯一的。在本文的回復(fù)中Member 7805758同學(xué)提供了另一種可能的實(shí)現(xiàn)。為了方便查看,我把它也列在下面:
- template <typename T>
 - class Buffer
 - {
 - std::string _name;
 - size_t _size;
 - std::unique_ptr<T[]> _buffer;
 - public:
 - // constructor
 - Buffer(const std::string& name = "", size_t size = 16):
 - _name(name),
 - _size(size),
 - _buffer(size? new T[size] : nullptr)
 - {}
 - // copy constructor
 - Buffer(const Buffer& copy):
 - _name(copy._name),
 - _size(copy._size),
 - _buffer(copy._size? new T[copy._size] : nullptr)
 - {
 - T* source = copy._buffer.get();
 - T* dest = _buffer.get();
 - std::copy(source, source + copy._size, dest);
 - }
 - // copy assignment operator
 - Buffer& operator=(Buffer copy)
 - {
 - swap(*this, copy);
 - return *this;
 - }
 - // move constructor
 - Buffer(Buffer&& temp):Buffer()
 - {
 - swap(*this, temp);
 - }
 - friend void swap(Buffer& first, Buffer& second) noexcept
 - {
 - using std::swap;
 - swap(first._name , second._name);
 - swap(first._size , second._size);
 - swap(first._buffer, second._buffer);
 - }
 - };
 
結(jié)論
關(guān)于C++11還有很多要說(shuō)的。本文只是各種入門(mén)介紹中的一個(gè)。本文展示了一系列C++開(kāi)發(fā)者應(yīng)當(dāng)使用的核心語(yǔ)言特性與標(biāo)準(zhǔn)庫(kù)函數(shù)。然而我建議你能更加深入地學(xué)習(xí),至少也要再看看本文所介紹的特性中的部分。
原文鏈接:http://www.codeproject.com/Articles/570638/Ten-Cplusplus11-Features-Every-Cplusplus-Developer
譯文鏈接:http://blog.jobbole.com/44015/















 
 
 











 
 
 
 