本文列出C++11的部分特性以及注意點與用途普气。
1. nullptr
- 為了解決的問題是:
void func(int);
void func(int *);
func(NULL); //這個函數(shù)調(diào)用會調(diào)用參數(shù)是int的版本谜疤;
- (1)nullptr的特點是可以隱式的轉(zhuǎn)化為任意指針類型(比如nullptr當做函數(shù)參數(shù)),但是不可以轉(zhuǎn)化為其他任何類型现诀,包括bool類型(if(nullptr)不允許)夷磕,reinterpret_cast強制轉(zhuǎn)化也不被允許。
(2)nullptr本質(zhì)是一個編譯期的常量仔沿,換句話說坐桩,其是一個右值,大小與指針一樣大(sizeof(nullptr) == sizeof((void*)0))封锉,是先有的nullptr绵跷,然后定義的nullptr_t
#define decltype(nullptr) nullptr_t
2. =default與=delete
1 編譯器為類中默認生成的成員函數(shù)有六個:
默認構(gòu)造函數(shù)
析構(gòu)函數(shù)
拷貝構(gòu)造函數(shù)
拷貝賦值函數(shù)
移動構(gòu)造函數(shù)
移動賦值函數(shù)
2 使用=default的好處
對定義了非默認構(gòu)造函數(shù)的類的默認構(gòu)造函數(shù)使用=default膘螟,可以使得類還有可能是POD類型。
POD類型的解釋抖坪,維基百科這樣說:
可見萍鲸,POD類類型就是指class闷叉、struct擦俐、union,且不具有用戶定義的構(gòu)造函數(shù)握侧、析構(gòu)函數(shù)蚯瞧、拷貝算子、賦值算子品擎;不具有繼承關(guān)系埋合,因此沒有基類;不具有虛函數(shù)萄传,所以就沒有虛表甚颂;非靜態(tài)數(shù)據(jù)成員沒有私有或保護屬性的、沒有引用類型的秀菱、沒有非POD類類型的(即嵌套類都必須是POD)振诬、沒有指針到成員類型的(因為這個類型內(nèi)含了this指針)。
POD類型的好處是:
- 與C語言的struct的內(nèi)存布局相同衍菱,所以可以和C語言結(jié)構(gòu)體交互使用赶么;
- 是拷貝不變的(trivially copyable)的class,可以使用memcpy脊串, memmove不改變它的語義辫呻。拷貝的時候調(diào)用這些函數(shù)是更快的琼锋。
使用上:
- 《STL源碼分析》中介紹了SGI STL用到的__type_traits放闺,大量的構(gòu)造函數(shù)和析構(gòu)函數(shù)在調(diào)用前會先根據(jù)__type_traits判斷一下是否存在trival的構(gòu)造函數(shù)和析構(gòu)函數(shù),如果是的話缕坎,就不進行循環(huán)怖侦,否則就需要進行。但使用這個的前提是用戶在自定義類中需要定義__type_traits中用到的類型的typedef念赶。
- C++11中支持了std::is_pod础钠,即不需要用戶在類中定義出來很多typedef,就可以判斷出是否是POD類型叉谜。
另:
std::is_pod的返回值是constexpr類型旗吁,意思是這個函數(shù)的調(diào)用結(jié)果在編譯期就可以得到,不會等到運行期才執(zhí)行停局。
3 使用=delete的好處
- 可以很方便地對編譯器自動生成拷貝操作符或者拷貝構(gòu)造等行為提供禁止很钓,不需要使用私有化這些函數(shù)或者將這些函數(shù)放到父類中設(shè)置為私有并不提供定義的方式來處理了香府。
- 可以禁止隱式的類型轉(zhuǎn)化,包括類的成員函數(shù)码倦,也包括全局的函數(shù)企孩。
void func(int);
void func(char) = delete; //此時就是禁止char隱式轉(zhuǎn)化為int;
- 可以實現(xiàn)禁止在堆中或者棧中分配內(nèi)存的操作
void* operator new(std::size_t) = delete; //禁止在堆中分配內(nèi)存袁稽;
~NoStackAlloc() = delete; //禁止在棧中分配內(nèi)存勿璃。(棧中的會自動調(diào)用析構(gòu),new出的需要人為delete才調(diào)用)
3. lambda函數(shù)
1 lambda函數(shù)可以在函數(shù)邏輯不是很復(fù)雜的情況下代替函數(shù)指針與仿函數(shù)的使用推汽。其行為類似于一個函數(shù)內(nèi)定義函數(shù)补疑。
2 lambda相對于函數(shù)指針的好處
- 函數(shù)指針默認不是inline的,lambda默認是inline的歹撒;
- 仿函數(shù)針對函數(shù)指針的好處就是存在初始狀態(tài)莲组,lambda也可以通過捕獲參數(shù)的方式來得到初始狀態(tài)。
lambda除了比仿函數(shù)要簡單暖夭,但缺點是:
lambda所能捕獲的參數(shù)只有父函數(shù)內(nèi)部的局部變量锹杈,更外層的變量是無法捕獲到的。
3 使用lambda需要注意的的問題
- 以值捕獲的情況下迈着,定義該lambda的時候會將捕獲到的變量保存到lambda函數(shù)內(nèi)部竭望,后期在lambda外部改變了被捕獲變量的值,不影響lambda函數(shù)內(nèi)部:
int a = 10;
auto lambda = [=]() {return a + 10;}; //定義了之后寥假,a就保存到lambda里面了
a = 30; //后期再怎么改
cout << lambda() << endl; //輸出也是20市框;
a = 20;
cout << lambda() << endl; //輸出也是20;
原因就是糕韧,lambda的實現(xiàn)原理是枫振,編譯器會在編譯期將其轉(zhuǎn)化為仿函數(shù),所以值以構(gòu)造函數(shù)的方式傳遞了進去就無法再改變了萤彩。
- 以引用方式捕獲的時候粪滤,lambda改變了被捕獲的數(shù)據(jù),外部可以感知到雀扶。
這里涉及到lambda是默認const屬性的杖小,含義是會默認將捕獲進來的變量定義為const類型,而引用類型還可以在里面改變的原因是a所引用的對象沒有變愚墓,變化的只是其值予权。成員變量是引用類型,在const函數(shù)內(nèi)是可以改變其值的浪册,如下:
class D {
public:
D(int a):d(a) {}
void change() const {
d = 10; //d是引用類型扫腺,在const函數(shù)中可以被改變
}
void print() const {
cout << d << endl;
}
private:
int& d;
};
int main()
{
int a = 20;
D d(a);
d.change();
d.print(); //結(jié)果輸出的是10;
}
- 引用方式捕獲的時候沒有以值捕獲時候(第1點)的那種問題村象。
4. 強枚舉類型
- enum有幾個特別的性質(zhì):
- enum內(nèi)部定義的常量的作用域會被拓展到enum所在作用域笆环,容易引起沖突攒至;
當然,將enum的定義放到特定的命名空間中躁劣,或者放到類中定義迫吐,則不會污染全局空間。- 存在于整數(shù)的隱式轉(zhuǎn)換账忘,表現(xiàn)為在比較的時候志膀,可以與整數(shù)做比較,可以與另一個enum對象內(nèi)的常量進行比較而不會報錯闪萄。
- enum定義的常量所占空間不是一定的梧却,與編譯器有關(guān)。
- 針對以上三個問題败去,提出強枚舉類型,在enum于類名之間加上class烈拒,也就對應(yīng)有如下好處:
- 合適的常量作用域圆裕,內(nèi)部常量必須通過enum類名來訪問,作用域不再是全局荆几。(enum class想要正常使用的話吓妆,必須有一個名字)
- 不能進行隱式轉(zhuǎn)換,但是可以進行顯示轉(zhuǎn)換吨铸;(也是每個常量對應(yīng)一個整數(shù)行拢,但是當做整數(shù)用時必須顯示轉(zhuǎn)化)
- 保存常量的數(shù)據(jù)結(jié)構(gòu)可以指定,在enum class name : type的方式指定诞吱,這樣就解決了常量所占空間不一致的問題舟奠。
5. 智能指針與內(nèi)存管理
- 幾個智能指針
- auto_ptr,C++11之前就有的房维,允許拷貝沼瘫,但是拷貝之后指針所有權(quán)易主;
- unique_ptr咙俩,也是只有一個unique_ptr可以擁有這個指針(內(nèi)存資源)耿戚,實現(xiàn)的方法是,=delete掉了拷貝構(gòu)造和拷貝運算符重載阿趁,但是定義了移動構(gòu)造函數(shù)和移動運算符重載膜蛔;
- shared_ptr則是可以有多個shared_ptr擁有這個內(nèi)存資源,采用的是引用計數(shù)的方式脖阵,但是會有循環(huán)引用的問題皂股;
- weak_ptr則是和shared_ptr搭配使用,可以認為其僅僅就是查詢shared_ptr管理的內(nèi)存的狀態(tài)独撇,比如引用計數(shù)是多少屑墨,釋放沒有躁锁,里面的值是什么,不占有這個資源(引用計數(shù)不加1)卵史,但是也可以查看狀態(tài)信息战转。
- shared_ptr存在循環(huán)引用的問題無法解決,可以的解決方式是:
采用基于跟蹤處理的垃圾回收器:
跟蹤是:從當前正在使用的對象(可能很多)開始以躯,找到活的對象槐秧,標記一下;
清除:將未被標記的對象釋放忧设;(存在內(nèi)存碎片)
整理:是將被標記的對象左對齊移動刁标;(存在移動的代價)
拷貝:將活的對象拷貝到另一個空間:TO空間,然后釋放FROM空間址晕,下次FROM與TO空間的用途反過來膀懈。(利用率低且需要移動)
- C++11中最小垃圾回收支持
由于指針的靈活性,導(dǎo)致垃圾回收器實現(xiàn)起來相當困難,比如指針的移動等,解決辦法可以是投储,由用戶來將移動后的指針指向的空間標記為不要清除函喉。
6. sizeof的拓展
sizeof可以用于類的成員變量了:
struct AAA {
int a; //public的才可以。
};
//使用上,需要使用類作用域標識符
cout << sizeof(AAA::a) << endl;
7. friend的拓展
friend聲明的類名可以不用加class了。
template<class T> struct AAA {
friend T; //如果T是基本類型,該句會被忽略掉
int a;
};
friend的一個用途是用于測試疑苫,測試類聲明為被測試類的friend,這樣就可以測試私有成員函數(shù)了纷责。friend被拓展后捍掺,就可以當做這個用途。
8. 新引入的final關(guān)鍵字和override關(guān)鍵字
- 說明一個現(xiàn)象:
class AAA {
public:
virtual void func() {
cout << "AAA" << endl;
}
};
class BBB : public AAA{
public:
void func() {
cout << "BBB" << endl;
}
};
class CCC :public BBB{
public:
void func() {
cout << "CCC" << endl;
}
};
//結(jié)論是輸出:CCC碰逸,即不論中間有沒有缺少virtual的乡小,只要父類指針有(AAA有),就可以找到正確的對象(CCC)饵史。(不會經(jīng)由BBB)
final的作用是禁止子類重寫函數(shù)(override);
override的作用是告訴編譯器我在重寫满钟,幫我檢查;
使用方法上,final和override是一樣的胳喷,都是在函數(shù)參數(shù)后邊湃番。
9. 名字查找規(guī)則引起的問題
名字查找規(guī)則,當調(diào)用一個類成員函數(shù)時吭露,其會當前類尋找吠撮,如果找到了,就不再向下尋找了讲竿,這個找到泥兰,是找到的名字弄屡,不包括參數(shù);所以當存在子類和父類的函數(shù)重載時鞋诗,通過子類對象是無法調(diào)用父類那個版本的函數(shù)的膀捷。
class AAA {
public:
void func() {
cout << "AAA" << endl;
}
};
class BBB : public AAA{
public:
void func(int a) {
cout << "BBB" << endl;
}
};
//通過如下方式調(diào)用func會報錯
BBB b;
b.func();
非指針或者引用的方式調(diào)用,在找到BBB中的func就不再尋找AAA中的了削彬,導(dǎo)致參數(shù)數(shù)量不匹配報錯全庸。
解決辦法是使用using關(guān)鍵字,可以繼承父類中的同名方法:
class AAA {
public:
void func() {
cout << "AAA" << endl;
}
};
class BBB : public AAA{
public:
using AAA::func; //強行拉取融痛,解決名字查找規(guī)則的問題
void func(int a) {
cout << "BBB" << endl;
}
};
using還有一個用途是子類想要繼承父類的構(gòu)造函數(shù)壶笼,也可以使用using AAA::AAA,此時AAA在BBB中就變成了BBB雁刷,免去了BBB為構(gòu)造AAA而寫的一系列轉(zhuǎn)發(fā)構(gòu)造函數(shù)覆劈。
以上還有兩個可能的問題:
(1)多個父類的構(gòu)造函數(shù)沖突如何處理:在子類中顯式定義出沖突的那個構(gòu)造函數(shù);
(2)繼承了父類的安券,子類中還存在成員變量如何賦值:使用成員變量就地初始化墩崩;
10. 委托構(gòu)造函數(shù)(有點水)
含義是,構(gòu)造函數(shù)可以在初始化列表中調(diào)用另一個構(gòu)造函數(shù)侯勉,但調(diào)用后不能再有初始化列表。這樣可以避免很多重復(fù)的定義铝阐。
一個用途是址貌,被調(diào)用的構(gòu)造函數(shù)被聲明成template時,可以實現(xiàn)模板構(gòu)造函數(shù)徘键。