lamda表達(dá)式
Lambda
表達(dá)式來(lái)源于函數(shù)式編程竟终,說(shuō)白就了就是在使用的地方定義函數(shù)何吝,有的語(yǔ)言叫“閉包。如果lambda
函數(shù)沒(méi)有傳回值(例如void
)允乐,其回返類型可被完全忽略篓吁。 定義在與 lambda 函數(shù)相同作用域的變量參考也可以被使用茫因。這種的變量集合一般被稱作closure
(閉包)。C++引入Lambda的最主要原因就是:
- 可以定義匿名函數(shù)
- 編譯器會(huì)把其轉(zhuǎn)成函數(shù)對(duì)象
Lambda表達(dá)式完整的聲明格式如下:
[capture list] (params list) mutable exception-> return type { function body }
各項(xiàng)具體含義如下
- capture list:捕獲外部變量列表
- params list:形參列表
- mutable指示符:用來(lái)說(shuō)用是否可以修改捕獲的變量
- exception:異常設(shè)定
- return type:返回類型
- function body:函數(shù)體
此外杖剪,我們還可以省略其中的某些成分來(lái)聲明“不完整”的Lambda表達(dá)式冻押,常見(jiàn)的有以下幾種:
[capture list] (params list) -> return type {function body}
[capture list] (params list) {function body}
[capture list] {function body}
- 格式1聲明了const類型的表達(dá)式驰贷,這種類型的表達(dá)式不能修改捕獲列表中的值。
- 格式2省略了返回值類型翼雀,但編譯器可以根據(jù)以下規(guī)則推斷出Lambda表達(dá)式的返回類型:
(1):如果function body中存在return語(yǔ)句饱苟,則該Lambda表達(dá)式的返回類型由return語(yǔ)句的返回類型確定;
(2):如果function body中沒(méi)有return語(yǔ)句狼渊,則返回值為void類型箱熬。 - 格式3中省略了參數(shù)列表,類似普通函數(shù)中的無(wú)參函數(shù)狈邑。
簡(jiǎn)單的例子:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(int a, int b) {
return a < b;
}
int main(int argc, char** argv) {
vector<int> myvec{ 3, 2, 5, 7, 3, 2 };
vector<int> lbvec(myvec);
sort(myvec.begin(), myvec.end(), cmp); // 舊式做法
cout << "predicate function:" << endl;
for (int it : myvec)
cout << it << ' ';
cout << endl;
sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; }); //Lambda表達(dá)式
cout << "lambda expression:" << endl;
for (auto it : lbvec)
cout << it << ' ';
}
自動(dòng)類型推導(dǎo)auto
auto并沒(méi)有讓C++成為弱類型語(yǔ)言城须,也沒(méi)有弱化變量,只是使用auto的時(shí)候米苹,編譯器根據(jù)上下文情況糕伐,確定auto變量的真正類型。auto可以用來(lái)定義變量蘸嘶,也可以作為函數(shù)的返回值良瞧。
auto addTest(int a, int b) {
return a + b;
}
int main(int argc, char** argv) {
auto index = 10;
auto ret = addTest(1,2);
std::cout << "index: " << index << std::endl;
std::cout << "res: " << ret << std::endl;
}
如上定義函數(shù)時(shí)可以使用auto作為返回值。但是auto只能用于定義函數(shù)训唱,不能用于聲明函數(shù)褥蚯。如果聲明在頭文件中,定義在代碼文件中况增,那么編譯將無(wú)法通過(guò)赞庶,但是可以把定義和聲明都在頭文件中,那么就可以了澳骤。所以一般不要這么使用auto歧强,在定義一些變量的時(shí)候使用就可。
而我一般不使用auto为肮,因?yàn)榇a習(xí)慣我想知道每個(gè)變量是什么類型摊册,特別是一些涉及到隱式轉(zhuǎn)換時(shí),會(huì)相當(dāng)?shù)牟幻睢?br>
另外auto與for循環(huán)也是相當(dāng)?shù)暮?jiǎn)潔颊艳。
int main(int argc, char** argv) {
int numbers[] = { 1,2,3,4,5 };
for (auto n : numbers) {
std::cout << n << std::endl;
}
}
以上用法不僅僅局限于數(shù)據(jù)丧靡,STL容器都同樣適用。
自動(dòng)化推導(dǎo)decltype
decltype是類型的推導(dǎo)籽暇,頗有一點(diǎn)跟auto是相反的意思。decltype的類型推導(dǎo)并不是像auto一樣是從變量聲明的初始化表達(dá)式獲得變量的類型饭庞,而是總是以一個(gè)普通表達(dá)式作為參數(shù)戒悠,返回該表達(dá)式的類型,而且decltype并不會(huì)對(duì)表達(dá)式進(jìn)行求值。
如下:
int i = 4;
decltype(i) a; //推導(dǎo)結(jié)果為int舟山。a的類型為int绸狐。
與using/typedef
合用卤恳,用于定義類型:
using size_t = decltype(sizeof(0)); //sizeof(a)的返回值為size_t類型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);
vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin(); i != vec.end(); i++) {
//...
}
重用匿名類型,在C++中,我們有時(shí)候會(huì)遇上一些匿名類型寒矿,而借助decltype突琳,我們可以重新使用這個(gè)匿名的結(jié)構(gòu)體:
struct {
int d ;
doubel b;
} anon_s;
decltype(anon_s) as; //定義了一個(gè)上面匿名的結(jié)構(gòu)體
最好的用處,泛型編程中結(jié)合auto符相,用于追蹤函數(shù)的返回值類型:
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(_Tx*_Ty) {
return x * y;
}
decltype推導(dǎo)四規(guī)則
- 如果e是一個(gè)沒(méi)有帶括號(hào)的標(biāo)記符表達(dá)式或者類成員訪問(wèn)表達(dá)式拆融,那么的decltype(e)就是e所命名的實(shí)體的類型。此外啊终,如果e是一個(gè)被重載的函數(shù)镜豹,則會(huì)導(dǎo)致編譯錯(cuò)誤。
- 假設(shè)e的類型是T蓝牲,如果e是一個(gè)將亡值趟脂,那么decltype(e)為T&&
- 假設(shè)e的類型是T,如果e是一個(gè)左值例衍,那么decltype(e)為T&昔期。
- 假設(shè)e的類型是T,則decltype(e)為T佛玄。
nullptr
C/C++的NULL宏是個(gè)有很多潛在BUG的宏硼一。因?yàn)橛械膸?kù)把其定義成整數(shù)0,有的定義成 (void*)0翎嫡。在C的時(shí)代還好欠动,但是在C++的時(shí)代,這就會(huì)引發(fā)很多問(wèn)題惑申。例如test(int a)
這個(gè)函數(shù)具伍,傳入了a=0
,這個(gè)時(shí)候你就不知道是傳入的NULL還是整數(shù)0圈驼。
delete和default函數(shù)
我們知道C++的編譯器在你沒(méi)有定義某些成員函數(shù)的時(shí)候會(huì)給你的類自動(dòng)生成這些函數(shù)人芽,比如,構(gòu)造函數(shù)绩脆,拷貝構(gòu)造萤厅,析構(gòu)函數(shù),賦值函數(shù)靴迫。有些時(shí)候惕味,我們不想要這些函數(shù),比如玉锌,構(gòu)造函數(shù)名挥,因?yàn)槲覀兿胱鰧?shí)現(xiàn)單例模式。傳統(tǒng)的做法是將其聲明成private類型主守。
在新的C++11
中引入了兩個(gè)指示符禀倔,delete
意為告訴編譯器不自動(dòng)產(chǎn)生這個(gè)函數(shù)榄融,default
告訴編譯器產(chǎn)生一個(gè)默認(rèn)的。例下:
struct A {
A() = default; //C++11
virtual ~A()=default; //C++11
};
struct NoCopy {
NoCopy & operator =( const NoCopy & ) = delete;
NoCopy ( const NoCopy & ) = delete;
};
NoCopy a;
NoCopy b(a); //compilation error, copy ctor is deleted
問(wèn)題出現(xiàn)了救湖,我們?yōu)槭裁葱枰觗efault愧杯,難道本身不就是default嗎?不全然是鞋既,比如構(gòu)造函數(shù)力九,因?yàn)橹灰愣x了一個(gè)構(gòu)造函數(shù),編譯器就不會(huì)給你生成一個(gè)默認(rèn)的涛救。所以畏邢,為了要讓默認(rèn)的和自定義的共存,才引入這個(gè)參數(shù)检吆。如下:
struct SomeType {
SomeType() = default; // 使用編譯器生成的默認(rèn)構(gòu)造函數(shù)
SomeType(OtherType value);
};
關(guān)于delete還有兩個(gè)有用的地方:
- 讓你的對(duì)象只能生成在棧內(nèi)存上
struct NonNewable {
void *operator new(std::size_t) = delete;
};
- 阻止函數(shù)的其形參的類型調(diào)用
void f(int i);
void f(double) = delete;
若嘗試以 double 的形參調(diào)用 f()舒萎,將會(huì)引發(fā)編譯期錯(cuò)誤, 編譯器不會(huì)自動(dòng)將 double 形參轉(zhuǎn)型為 int 再調(diào)用f()蹭沛,如果傳入的參數(shù)是double臂寝,則會(huì)出現(xiàn)編譯錯(cuò)誤。
線程庫(kù)
std::thread為C++11的線程類咆贬,使用方法和boost接口一樣,非常方便帚呼。同時(shí)掏缎,C++11的std::thread解決了boost::thread中構(gòu)成參數(shù)限制的問(wèn)題,我想著都是得益于C++11的可變參數(shù)的設(shè)計(jì)風(fēng)格煤杀。
#include <thread>
void work_thread1() {
std::cout << "work_thread1 - 1\r\n" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "work_thread1 - 2" << std::endl;
}
void work_thread2(int iParam, std::string sParam) {
std::cout << "work_thread2 - 1" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "work_thread2 - 2" << std::endl;
}
int main(int argc, char** argv) {
std::thread t1(work_thread1);
std::thread t2(work_thread2, 10, "abc");
t1.join();
std::cout << "join" << std::endl;
t2.detach();
std::cout << "detach" << std::endl;
}
新型智能指針
C++程序設(shè)計(jì)中使用堆內(nèi)存是非常頻繁的操作眷蜈,堆內(nèi)存的申請(qǐng)和釋放都由程序員自己管理。程序員自己管理堆內(nèi)存可以提高了程序的效率沈自,但是整體來(lái)說(shuō)堆內(nèi)存的管理是麻煩的酌儒,C++11中引入了智能指針的概念,方便管理堆內(nèi)存枯途。使用普通指針忌怎,容易造成堆內(nèi)存泄露(忘記釋放),二次釋放酪夷,程序發(fā)生異常時(shí)內(nèi)存泄露等問(wèn)題等榴啸,使用智能指針能更好的管理堆內(nèi)存。
理解智能指針需要從下面三個(gè)層次:
- 從較淺的層面看晚岭,智能指針是利用了一種叫做RAII(資源獲取即初始化)的技術(shù)對(duì)普通的指針進(jìn)行封裝插掂,這使得智能指針實(shí)質(zhì)是一個(gè)對(duì)象,行為表現(xiàn)的卻像一個(gè)指針。
- 智能指針的作用是防止忘記調(diào)用delete釋放內(nèi)存和程序異常的進(jìn)入catch塊忘記釋放內(nèi)存辅甥。另外指針的釋放時(shí)機(jī)也是非常有考究的,多次釋放同一個(gè)指針會(huì)造成程序崩潰燎竖,這些都可以通過(guò)智能指針來(lái)解決璃弄。
- 智能指針還有一個(gè)作用是把值語(yǔ)義轉(zhuǎn)換成引用語(yǔ)義。
包含在頭文件<memory>中构回,shared_ptr
夏块、unique_ptr
、weak_ptr
shared_ptr
shared_ptr多個(gè)指針指向相同的對(duì)象纤掸。shared_ptr使用引用計(jì)數(shù)脐供,每一個(gè)shared_ptr的拷貝都指向相同的內(nèi)存。每使用他一次借跪,內(nèi)部的引用計(jì)數(shù)加1政己,每析構(gòu)一次,內(nèi)部的引用計(jì)數(shù)減1掏愁,減為0時(shí)歇由,自動(dòng)刪除所指向的堆內(nèi)存。shared_ptr內(nèi)部的引用計(jì)數(shù)是線程安全的果港,但是對(duì)象的讀取需要加鎖沦泌。
- 初始化。智能指針是個(gè)模板類辛掠,可以指定類型谢谦,傳入指針通過(guò)構(gòu)造函數(shù)初始化。也可以使用make_shared函數(shù)初始化萝衩。不能將指針直接賦值給一個(gè)智能指針回挽,一個(gè)是類,一個(gè)是指針欠气。例如std::shared_ptr<int> p4 = new int(1);的寫法是錯(cuò)誤的
- 拷貝和賦值厅各。拷貝使得對(duì)象的引用計(jì)數(shù)增加1预柒,賦值使得原對(duì)象引用計(jì)數(shù)減1队塘,當(dāng)計(jì)數(shù)為0時(shí),自動(dòng)釋放內(nèi)存宜鸯。后來(lái)指向的對(duì)象引用計(jì)數(shù)加1憔古,指向后來(lái)的對(duì)象
- get函數(shù)獲取原始指針
- 注意不要用一個(gè)原始指針初始化多個(gè)shared_ptr,否則會(huì)造成二次釋放同一內(nèi)存
- 注意避免循環(huán)引用淋袖,shared_ptr的一個(gè)最大的陷阱是循環(huán)引用鸿市,循環(huán),循環(huán)引用會(huì)導(dǎo)致堆內(nèi)存無(wú)法正確釋放,導(dǎo)致內(nèi)存泄漏焰情。循環(huán)引用在weak_ptr中介紹
例:
#include <iostream>
#include <memory>
int main() {
int a = 10;
std::shared_ptr<int> ptra = std::make_shared<int>(a);
std::shared_ptr<int> ptra2(ptra); //copy
std::cout << ptra.use_count() << std::endl;
int b = 20;
int *pb = &a;
//std::shared_ptr<int> ptrb = pb; //error
std::shared_ptr<int> ptrb = std::make_shared<int>(b);
ptra2 = ptrb; //assign
pb = ptrb.get(); //獲取原始指針
std::cout << ptra.use_count() << std::endl;
std::cout << ptrb.use_count() << std::endl;
}
運(yùn)行結(jié)果:
unique_ptr
unique_ptr唯一擁有其所指對(duì)象陌凳,同一時(shí)刻只能有一個(gè)unique_ptr指向給定對(duì)象(通過(guò)禁止拷貝語(yǔ)義、只有移動(dòng)語(yǔ)義來(lái)實(shí)現(xiàn))内舟。相比與原始指針unique_ptr用于其RAII的特性合敦,使得在出現(xiàn)異常的情況下,動(dòng)態(tài)資源能得到釋放验游。unique_ptr指針本身的生命周期:從unique_ptr指針創(chuàng)建時(shí)開(kāi)始充岛,直到離開(kāi)作用域。離開(kāi)作用域時(shí)耕蝉,若其指向?qū)ο蟠薰#瑒t將其所指對(duì)象銷毀(默認(rèn)使用delete操作符,用戶可指定其他操作)垒在。unique_ptr指針與其所指對(duì)象的關(guān)系:在智能指針生命周期內(nèi)蒜魄,可以改變智能指針?biāo)笇?duì)象,如創(chuàng)建智能指針時(shí)通過(guò)構(gòu)造函數(shù)指定爪膊、通過(guò)reset方法重新指定权悟、通過(guò)release方法釋放所有權(quán)、通過(guò)移動(dòng)語(yǔ)義轉(zhuǎn)移所有權(quán)推盛。
例:
#include <iostream>
#include <memory>
int main(int argc, char** argv) {
{
std::unique_ptr<int> uptr(new int(10)); //綁定動(dòng)態(tài)對(duì)象
//std::unique_ptr<int> uptr2 = uptr; //不能賦值
//std::unique_ptr<int> uptr2(uptr); //不能拷貝
std::unique_ptr<int> uptr2 = std::move(uptr); //轉(zhuǎn)換所有權(quán)
uptr2.release(); //釋放所有權(quán)
}
//超過(guò)uptr的作用域峦阁,內(nèi)存釋放
}
weak_ptr
weak_ptr是為了配合shared_ptr而引入的一種智能指針,因?yàn)樗痪哂衅胀ㄖ羔樀男袨樵懦桑瑳](méi)有重載operator*和->,它的最大作用在于協(xié)助shared_ptr工作榔昔,像旁觀者那樣觀測(cè)資源的使用情況。weak_ptr可以從一個(gè)shared_ptr或者另一個(gè)weak_ptr對(duì)象構(gòu)造瘪菌,獲得資源的觀測(cè)權(quán)撒会。但weak_ptr沒(méi)有共享資源,它的構(gòu)造不會(huì)引起指針引用計(jì)數(shù)的增加师妙。使用weak_ptr的成員函數(shù)use_count()可以觀測(cè)資源的引用計(jì)數(shù)诵肛,另一個(gè)成員函數(shù)expired()的功能等價(jià)于use_count()==0,但更快,表示被觀測(cè)的資源(也就是shared_ptr的管理的資源)已經(jīng)不復(fù)存在默穴。weak_ptr可以使用一個(gè)非常重要的成員函數(shù)lock()從被觀測(cè)的shared_ptr獲得一個(gè)可用的shared_ptr對(duì)象怔檩, 從而操作資源。但當(dāng)expired()==true的時(shí)候蓄诽,lock()函數(shù)將返回一個(gè)存儲(chǔ)空指針的shared_ptr薛训。
例:
#include <iostream>
#include <memory>
int main(int argc, char** argv) {
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr);
std::cout << wp.use_count() << std::endl;
if(!wp.expired()) {
std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
*sh_ptr = 100;
std::cout << wp.use_count() << std::endl;
}
}
//delete memory
}
右值引用和move語(yǔ)義
在老版的C++中,臨時(shí)性變量(稱為右值”R-values”仑氛,位于賦值操作符之右)經(jīng)常用作交換兩個(gè)變量乙埃。比如下面的示例中的tmp變量闸英。示例中的那個(gè)函數(shù)需要傳遞兩個(gè)string的引用,但是在交換的過(guò)程中產(chǎn)生了對(duì)象的構(gòu)造介袜,內(nèi)存的分配還有對(duì)象的拷貝構(gòu)造等等動(dòng)作甫何,成本比較高。
void naiveswap_(string &a, string &b) {
string temp = a;
a=b;
b=temp;
}
C++ 11增加一個(gè)新的引用(reference)類型稱作右值引用(R-value reference)米酬,標(biāo)記為typename &&沛豌。他們能夠以non-const值的方式傳入,允許對(duì)象去改動(dòng)他們赃额。這項(xiàng)修正允許特定對(duì)象創(chuàng)造出move語(yǔ)義。
舉例而言叫确,上面那個(gè)例子中跳芳,string類中保存了一個(gè)動(dòng)態(tài)內(nèi)存分存的char指針,如果一個(gè)string對(duì)象發(fā)生拷貝構(gòu)造(如:函數(shù)返回)竹勉,string類里的char內(nèi)存只能通過(guò)創(chuàng)建一個(gè)新的臨時(shí)對(duì)象飞盆,并把函數(shù)內(nèi)的對(duì)象的內(nèi)存copy到這個(gè)新的對(duì)象中,然后銷毀臨時(shí)對(duì)象及其內(nèi)存次乓,這是原來(lái)C++性能上重點(diǎn)被批評(píng)的事吓歇。
能過(guò)右值引用,string的構(gòu)造函數(shù)需要改成“move構(gòu)造函數(shù)”票腰,如下所示城看。這樣一來(lái),使得對(duì)某個(gè)stirng的右值引用可以單純地從右值復(fù)制其內(nèi)部C-style的指針到新的string杏慰,然后留下空的右值测柠。這個(gè)操作不需要內(nèi)存數(shù)組的復(fù)制,而且空的暫時(shí)對(duì)象的析構(gòu)也不會(huì)釋放內(nèi)存缘滥,其更有效率轰胁。
class string {
string (string&&); //move constructor
string&& operator=(string&&); //move assignment operator
};
The C++11 STL中廣泛地使用了右值引用和move語(yǔ)議。因此朝扼,很多算法和容器的性能都被優(yōu)化了赃阀。