常用C++11特性

詳細介紹: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章