詳細介紹:C++11/14 高速上手教程
一、右值引用與std::move
如下代碼:
std::string tmp("bert")
std::string name(tmp)
第一行聲明瞭一個對象tmp,用"bert"字符串進行初始化
第二行聲明瞭一個對象name,使用參數tmp調用string的構造函數,因此對象name複製了"bert"的一個副本,如下:
如下代碼:
std::string tmp(“bert”);
std::string name(std::move(tmp));
直接將tmp中的內容移動到name中,而不是複製一份,用圖表示爲:
二、智能指針
-
unique_ptr
#include <string.h> #include <stdio.h> #include <memory> #include <iostream> using namespace std; class Student { public: Student() { cout << "Construct student" << endl; } ~Student() { cout << "Destruct student" << endl; } bool Register() { return true; } bool Enroll() { return true; } }; int main() { Student* p = new Student(); if (!p->Register()) { delete p; return -1; } if (!p->Enroll()) { delete p; return -2; } delete p; return 0; }
該代碼使用 new 在堆上分配一個 Student 對象,並對其調用了兩個成員函數:Register 和 Enroll。任意一個失敗都將返回失敗,並且不能忘記 delete 資源。在三處 return 語句之前,必須加上 delete p;否則就可能造成資源泄漏;如果使用 unique_ptr,就可以在指針 p 退出作用域時,自動釋放所分配的資源,完整代碼如下:
#include <string.h> #include <stdio.h> #include <memory> #include <iostream> using namespace std; class Student { public: Student() { cout << "Construct student" << endl; } ~Student() { cout << "Destruct student" << endl; } bool Register(Student* p) { return true; } bool Enroll(Student* p) { return true; } }; // 編譯 g++ -std=c++11 -o unique_ptr_test unique_ptr_test.cc int main() { unique_ptr<Student> p(new Student()); if (!p->Register()) { // 不需要任何操作,p指向的資源會自動釋放 return -1; } if (!p->Enroll()) { // 不需要任何操作,p指向的資源會自動釋放 return -2; } // 不需要任何操作,p指向的資源會自動釋放 return 0; }
利用 unique_ptr,無須操心釋放資源,避免內存泄漏。
另外,unique_ptr 也適用於消除“內部分配,外部釋放”這種易錯的機制,比如 c 語言的 strdup 函數:
int main() { // strdup函數內部爲字符串分配內存,並返回其地址 char* p = strdup("bert"); // ...使用p // 使用者必須釋放strdup分配的內存,否則內存泄漏 free(p); return 0; }
可以看到 strdup 函數的使用,給使用者打開了犯錯的窗戶,一不小心就會內存泄漏。而使用 unique_ptr 則可以解決這一問題:
std::unique_ptr<char []> my_strdup(const char* s) { if (s == nullptr) return std::unique_ptr<char []>(nullptr); //計算字符串長度 size_t len = strlen(s); //用智能指針管理分配的內存 std::unique_ptr<char []> str(new char[len+1]); //拷貝字符串s strcpy(str.get(), s); //返回分配的新字符串 return str; } int main() { auto p = my_strdup("bert"); // ... // 使用p // 使用者什麼都不需要做,無須擔心內存泄漏 return 0; }
unique_ptr 是舊標準庫中 auto_ptr 的升級版,由於獨佔性語義,它不允許被複制,但是可以 move:
std::unique_ptr p(new Student()); std::unique_ptr p2(std::move(p));
所以,unique_ptr 是可以被放到 STL 容器中;舊版本要求容器中的元素必須可以被複制,而現在放寬了:movable 的對象也是可以放入容器的:
std::vector<std::unique_ptr<int>> ptr_vec; ptr_vec.push_back(std::unique_ptr<int>(new int(123)));
-
shared_ptr
shared-ptr 是功能非常強大的智能指針,和 unique_ptr 不同的是,它是基於引用計數的,可以被多個所有者共同持有;當最後一個持有者退出作用域時,資源自動釋放;它完美解決了資源釋放時機的問題,試想一下多個裸指針指向同一個資源時,釋放資源將是多麼頭疼和難以正確,甚至需要使用觀察者模式來幫助使用者清理指針。而shared-ptr 完美解決了這一問題,完整代碼如下:
#include <string.h> #include <stdio.h> #include <memory> #include <thread> #include <iostream> using namespace std; typedef int Resourse; shared_ptr<Resourse> CreateResourse() { return make_shared<Resourse>(1); } void User1(shared_ptr<Resourse> p) { if (p) { //1 使用p cout << "use p in user1 thread." << endl; } return; } void User2(shared_ptr<Resourse> p) { this_thread::sleep_for(chrono::milliseconds(10)); if (p) { //2 使用p cout << "use p in user2 thread." << endl; } return; } int main() { shared_ptr<Resourse> res = CreateResourse(); // 啓動線程t1和t2,運行user函數,使用res thread t1(User1, res); thread t2(User2, res); // 等待線程結束,誰也不需要考慮res資源的釋放 t1.join(); t2.join(); return 0; }
shared_ptr 類似於 java 中的引用:多個引用指向一個對象,只有最後一個引用失效時候,該對象纔可以被垃圾回收。
-
weak_ptr
weak_ptr 是配合 shared_ptr 存在,主要是爲了解決兩個問題:一是循環引用問題,使得資源無法釋放;
例如 A 對象含有一個shared_ptr<B>
,而 B 對象也含有一個shared_ptr<A>
,那麼很容易產生循環引用,使得內存無法釋放。weak_ptr 要解決的另外一個問題是臭名昭著的懸掛指針(dangling pointer):指針指向的內存被刪除;一個簡單的場景是,A 線程創建資源,並傳遞給 B 線程,B 線程只讀訪問資源;但是 A 線程隨後可能釋放了資源,B 沒有感知,而得到了一個懸掛指針。#include <string.h> #include <stdio.h> #include <memory> #include <thread> #include <iostream> using namespace std; typedef int Resourse; shared_ptr<Resourse> g_resourse; void thread_a() { // 2. 創建全局資源 g_resourse = make_shared<Resourse>(1); // 3. 睡眠3秒鐘 this_thread::sleep_for(chrono::seconds(3)); // 6. 釋放資源 g_resourse = nullptr; cout << "free resourse, thread A exit." << endl; } void thread_b() { // 1. 休眠,讓線程A先創建資源 this_thread::sleep_for(chrono::milliseconds(100)); // 4. 創建weak_ptr訪問資源,它可以有效檢測出懸掛指針: weak_ptr<Resourse> pw(g_resourse); // 5. 隔一秒鐘訪問資源,若資源被釋放了,則退出線程; int i = 0; while (1) { i++; // 調用weak_ptr的lock()嘗試提升到shared_ptr auto res(pw.lock()); if (res) { // 在6之前: 提升成功,指針res有效,可以使用資源,然後睡眠1秒鐘 cout << i << ":Success read resourse from thread B." << endl; this_thread::sleep_for(chrono::seconds(1)); } else { cout << "Fail read resourse from thread B, exit." << endl; return; // 7. 說明資源被釋放了,出現了"懸掛指針"情況,線程退出 } } } int main() { //啓動線程A std::thread t_a(thread_a); //啓動線程B std::thread t_b(thread_b); // 請注意看線程代碼註釋中的序號,大致代表了代碼的執行順序 //等待線程結束 t_a.join(); t_b.join(); return 0; }
輸出:
1:Success read resourse from thread B. 2:Success read resourse from thread B. 3:Success read resourse from thread B. free resourse, thread A exit. Fail read resourse from thread B, exit.
首先 A 線程釋放了資源並退出,然後 B 線程使用 weak_ptr 感知到了資源釋放,避免了出現懸掛指針錯誤
三、lambda,bind,function
C++11引入了lambda,它極大的方便了程序員編寫臨時函數,甚至可以模擬閉包。而bind可以適配函數簽名,及其靈活。
#include <string.h>
#include <stdio.h>
#include <memory>
#include <functional>
#include <iostream>
using namespace std;
int main() {
int r = 0;
// lambda,是一個可執行對象,類型是void (int* )
auto updateLambda = [](int* res) { (*res)++; };
// 將lambda賦值給callback,後者類型是 void (), 由於簽名不符,需要bind做適配
std::function<void ()> callback = std::bind(updateLambda, &r);
// 執行callback
callback();
// 現在,r應該遞增爲1
cout << "From main: r = " << r << endl;
return 0;
}
輸出
From main: r = 1
使用 bind 將 void (int*)類型的 lambda,適配到類型爲 void ()的 function
四、語法糖:auto,foreach
以前聲明一個迭代器:
map<int, int> mm;
map<int, int>::const_iterator it(mm.begin());
現在只需要:
auto it(mm.begin());
以前遍歷 map:
for (map<int, int>::const_iterator it(mm.begin()); it != mm.end(); ++it) {
cout << it->first << “ -> ” << it->second << endl;
}
現在:
for (const auto& kv : mm) {
cout << kv.first << “ -> ” << kv.second << endl;
}