【Cpp】第十四章-智能指針

智能指針

基礎概念

爲什麼要有智能指針

  首先先看一段程序,看看這段程序可能會出現什麼問題。

#include <iostream>
using namespace std;
void Func()
{
    int* ptr = new int[10];
    //...假設這其中有很多代碼
    throw "error!";
    // delete[] ptr;
}
int main()
{
    try
    {
        Func();
    }
    catch(const char* str)
    {
        cout << str;
    }
}

  首先這個代碼中我們由於可能因爲代碼過於複雜而忘記釋放空間,導致內存泄露,其次如果我們在這段代碼中拋出異常,導致函數終止也會導致無法釋空間,內存泄露,這種問題也被稱爲異常安全問題。作爲Cpp程序猿,我們最煩也是最應該提防的一個問題就是內存泄露。那麼針對這種情況我們象讓我們動態分配的內存空間可以在生命週期結束後自動釋放該怎麼辦呢?

什麼是智能指針

  智能指針是一個用於管理和存儲指針的類,它利用對象出了作用域自動調用析構函數的特性幫助我們釋放空間。接下來模擬實現一個簡單的智能指針類。

#include <iostream>
#include <string>
#include <memory>
template<class T>
class SmartPtr
{
public:
    SmartPtr(T* ptr)
        :_ptr(ptr)
    {
        std::cout << "construct smartptr" << std::endl;
    }
    ~SmartPtr()
    {
        if(_ptr)
        {
            delete _ptr;    
        }
        std::cout << "destory smartptr" << std::endl;
    }
private:
    T* _ptr;
};
void Func()
{
    std::string* ptr = new std::string;
    SmartPtr<std::string> smartPtr(ptr);
}
int main()
{
    Func();
}


construct smartptr
destory smartptr

  以上這個智能指針類就已經基本實現了幫助我們在指針聲明週期結束後自動釋放內存空間的功能,這種與之類似的功能實現在Cpp中稱之爲RAII(Resource Acquisition Is Initialization),資源獲取即初始化,是Cpp中常用的用於管理資源的方法,由這套方法產生了智能指針,當然還有智能鎖,幾乎所有我們在使用完必須釋放資源的類型都可以使用這套方法進行資源管理。這套資源管理的方法使得我們不需要顯示的釋放資源,並且使資源在其生命週期內長期有效,出了作用域後自動幫助我們釋放資源防止出現內存泄露的問題。
  我們之前實現的只能髮指針類並沒有實現完全,因爲作爲智能指針我們必須要求它能夠像指針一樣進行使用。

#include <iostream>
#include <string>
#include <memory>
template<class T>
class SmartPtr
{
public:
    //資源管理
    SmartPtr(T* ptr)
        :_ptr(ptr)
    {
        std::cout << "construct smartptr" << std::endl;
    }
    ~SmartPtr()
    {
        if(_ptr)
        {
            delete _ptr;    
        }
        std::cout << "destory smartptr" << std::endl;
    }
    //使其可以像指針一樣使用
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
};
void Func()
{
    std::string* ptr = new std::string("Hello");
    SmartPtr<std::string> smartPtr(ptr);
    std::cout << *smartPtr << std::endl;
    std::cout << smartPtr->size() << std::endl;
}
int main()
{
    Func();
}


construct smartptr
Hello
5
destory smartptr

  這樣就可以稱爲一個較爲完整的智能指針了,但是單單這樣還不夠,因爲這個類中最重要的也是最難處理的兩個默認函數還沒有書寫,即拷貝構造函數賦值運算符重載函數,這兩個函數的處理也苦惱了Cpp標準庫中的多種智能指針,接下來我們就要着重討論標準庫中對這兩個函數的處理。

庫中的智能指針

  早在C++98的版本中就已經出現了智能指針,它叫auto_ptr,但是由於十分不好用於是後面在C++11中出現了unique_ptrshared_ptr。不同的智能指針除了拷貝構造和賦值運算符重載函數的實現外其他都是大同小異,我們這裏着重討論他們在拷貝構造函數和賦值運算符重載函數上的不同。
  爲什麼不同的智能指針在這兩個默認成員函數上實現區別會很大呢?我們稍微思索即可發現,指針的拷貝和賦值往往和資源何時釋放而掛鉤。如果一個智能指針A拷貝了另一個智能指針B,而當B出了聲明週期此時要不要釋放資源呢?如果釋放資源那麼我們再次使用指針A就會有未定義行爲的發生。但如果不是放那麼何時又該釋放資源呢?這些問題的處理上使得智能指針有了差別。

auto_ptr

  auto_ptr誕生在C++98的標準庫中,但是這也是問題最多的智能指針,他對拷貝構造即賦值運算符重載的處理我們可以用一句話總結,即管理權限轉移,我們以下作個簡單的例子。注意:auto_ptrC++11中已經被刪除,因此我們可以依照文檔自己實現一個進行試驗。

#include <iostream>
#include <string>
#include <memory>
template<class T>
class auto_ptr
{
public:
    //資源管理
    auto_ptr(T* ptr)
        :_ptr(ptr)
    {
        std::cout << "construct smartptr" << std::endl;
    }
    ~auto_ptr()
    {
        if(_ptr)
        {
            delete _ptr;    
        }
        std::cout << "destory smartptr" << std::endl;
    }
    //使其可以像指針一樣使用
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
    //拷貝構造和賦值運算符重載
    auto_ptr(auto_ptr& ptr)
    {
        _ptr = ptr._ptr;
        ptr._ptr = nullptr;
        std::cout << "copy construct" << std::endl;
    }
    auto_ptr& operator=(auto_ptr& ptr)
    {
        if(this != &ptr)
        {
            if(_ptr)
            {
                delete _ptr;
            }
            _ptr = ptr._ptr;
            ptr._ptr = nullptr;
        }
        std::cout << "operator=()" << std::endl;
    }
private:
    T* _ptr;
};
void Func()
{
    std::string* ptr = new std::string("Hello");
    auto_ptr<std::string> autoptr(ptr);
    auto_ptr<std::string> copy(autoptr);
    std::cout << *copy << std::endl;
    std::cout << copy->size() << std::endl;
    std::cout << *autoptr << std::endl;
    std::cout << autoptr->size() << std::endl;
}
int main()
{
    Func();
}


construct smartptr
copy construct
Hello
5
崩潰...

  在這個模擬實驗中可以看出auto_ptr管理權限轉移的意義,它會在賦值或者拷貝後將原本的智能指針中所管理指針置空,如果我們再去訪問原來的智能指針,結果就是未定義行爲,程序崩潰,這指針真的太惡劣了。因此強烈不建議使用auto_ptr,不過好在庫中已經刪除了他,想用也沒的用了,皆大歡喜。

unique_ptr

  unique_ptrC++11中智能指針的改進版本,它解決了auto_ptr可能會導致程序崩潰的問題,但是他解決的方法略爲粗暴,即禁用拷貝構造和賦值。不讓拷貝不就不會出現問題了,簡單粗暴。

#include <iostream>
#include <memory>
#include <string>
int main()
{
    std::string* ptr = new std::string("Hello");
    std::unique_ptr<std::string> uniqueptr(ptr);
    std::cout << *uniqueptr << std::endl;
    std::cout << uniqueptr->size() << std::endl;
    //拷貝構造和賦值被禁用,防拷貝
    std::unique_ptr<std::string> copy(uniqueptr);//編譯不通過
    std::unique_ptr<std::string> copy2 = uniqueptr;//編譯不通過
}

  防拷貝很好的解決了auto_ptr的殘留問題,我們也模擬實現一個unique_ptr

#include <iostream>
#include <memory>
#include <string>
template<class T>
class Unique_Ptr
{
public:
    Unique_Ptr(T* ptr)
        :_ptr(ptr)
    {
        std::cout << "construct Unique_ptr" << std::endl;
    }
    ~Unique_Ptr()
    {
        if(_ptr)
        {
            delete _ptr;
        }
        std::cout << "destroy Unique_ptr" << std::endl;
    }
    //使其可以像指針一樣使用
    T* operator->()
    {
        return _ptr;
    }
    T& operator*()
    {
        if(_ptr)
        {
            return *_ptr;
        }
    }
private:
    //禁用拷貝構造和賦值運算符重載
    Unique_Ptr(Unique_Ptr& ptr);
    Unique_Ptr& operator=(Unique_Ptr& ptr);
    T* _ptr;
};
void Func()
{
    std::string* ptr = new std::string("Hello");
    Unique_Ptr<std::string> uniqueptr(ptr);
    std::cout << *uniqueptr << std::endl;
    std::cout << uniqueptr->size() << std::endl;
    //Unique_Ptr<std::string> copy(uniqueptr);//編譯不通過
    //Unique_Ptr<std::string> copy2 = uniqueptr;//編譯不通過
}
int main()
{
    Func();
}


construct Unique_ptr
Hello
5
destroy Unique_ptr

  這個版本的智能指針十分好用,因爲簡單方便因此很少出問題,而且我們本來就不建議讓智能指針出現拷貝,因爲可能會引發一系列問題,所以這個版本的智能指針也是最爲推薦使用的版本,如果要求必須可以進行拷貝那麼還得考慮接下來的版本。

shared_ptr

  shared_ptr是標準庫中的支持拷貝和賦值的智能指針。

#include <iostream>
#include <memory>
#include <string>
int main()
{
    std::string* ptr = new std::string("Hello");
    std::string* ptr2 = new std::string("World");
    std::shared_ptr<std::string> sharedptr(ptr);
    //拷貝構造
    std::shared_ptr<std::string> copy(sharedptr);
    std::shared_ptr<std::string> copy2(ptr2);
    std::cout << *copy2 << std::endl;
    //賦值
    copy2 = copy;
    std::cout << *sharedptr << std::endl;
    std::cout << copy->size() << std::endl;
    std::cout << *copy2 << std::endl;
}


World
Hello
5
Hello

  以上例子可以看出三個通過通過不同方式得來的智能指針都可以正常使用,雖然他們都指向相同的資源但是也可以很好的進行資源管理。shared_ptr之所以可以支持拷貝和賦值,是因爲它對智能指針拷貝的處理是通過一個引用計數,引用計數用來保存當前資源被幾個智能指針共享。
  當新增一個智能指針指向某個資源時則引用計數+1,一個智能指針不再指向某個資源則引用計數-1,當引用計數爲0時則說明已經沒有智能指針指向這個資源則釋放資源,否則不釋放資源。
  但是由於可能會有好幾個智能指針共同維護同一份引用計數的情況,於是如果在多線程中引用計數可能會同時被多個線程進行操作,稱爲臨界資源,於是就要考慮線程安全問題,爲了解決這些問題就需要在適當的地方加鎖,或者讓其成爲原子操作。
  同樣的這裏模擬實現一份shared_ptr瞭解原理。

#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <mutex>
#include <vector>
template<class T>
class Shared_ptr
{
public:
    //構造函數,應該初始化引用計數爲1,並且初始化互斥鎖
    Shared_ptr(T* ptr)
        :_ptr(ptr)
        ,_refCount(new int(1))
        ,_mtx(new std::mutex)
    {
        std::cout << "construct" << std::endl;
    }
    //拷貝構造
    Shared_ptr(const Shared_ptr<T>& temp)
        :_ptr(temp._ptr)
        ,_refCount(temp._refCount)
        ,_mtx(temp._mtx)
    {
        std::cout << "copy construct" << std::endl;
        AddRefCount();
    }
    //賦值運算符重載
    Shared_ptr& operator=(const Shared_ptr<T>& temp)
    {
        std::cout << "operator=" << std::endl;
        if(&temp != this)
        {
            Release();
            _ptr = temp._ptr;
            _refCount = temp._refCount;
            _mtx = temp._mtx;
            AddRefCount();
        }
        return *this;
    }
    //析構函數
    ~Shared_ptr()
    {
        std::cout << "destroy" << std::endl;
        Release();
    }
    //返回當前引用計數
    int RefCount()
    {
        return *_refCount;
    }
    //像指針一樣使用
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
private:
    //指針不再指向當前資源
    void Release()
    {
        //操作臨界資源,加鎖
        bool isRelease = false;
        _mtx->lock();
        //已經沒有指針在控制當前資源
        if(--(*_refCount) <= 0)
        {
            //釋放資源以及引用計數
            std::cout << "free resouce" << std::endl;
            delete _ptr;
            delete _refCount;
            isRelease = true;
        }
        _mtx->unlock();
        //鎖不能在加鎖中釋放,因此要在鎖中判斷是否需要釋放
        //最後在鎖外釋放鎖的資源
        if(isRelease == true)
        {
            delete _mtx;
        }
    }
    //有新的智能指針指向當前資源,增加引用計數
    void AddRefCount()
    {
        //同樣操作臨界資源加鎖
        _mtx->lock();
        ++(*_refCount);
        _mtx->unlock();
    }
private:
    T* _ptr;
    int* _refCount;
    std::mutex* _mtx;//互斥鎖
};
void Test1()
{
    std::cout << "Test1:" << std::endl;
    std::string* str = new std::string("Hello");
    std::string* str2 = new std::string("World");
    Shared_ptr<std::string> sharedPtr(str);
    Shared_ptr<std::string> sharedPtr2(sharedPtr);
    sharedPtr2 = Shared_ptr<std::string>(str2);
    std::cout << *sharedPtr << std::endl;
    std::cout << *sharedPtr2 << std::endl;
}

void thr_start()
{
    std::string* str = new std::string("Hello");
    std::vector<Shared_ptr<std::string>> sharedPtr(10, str);
    std::cout << sharedPtr[0].RefCount() << std::endl;
}
void Test2()
{
    std::cout << "Test2:" << std::endl;
    std::thread thr1(thr_start);
    std::thread thr2(thr_start);
    thr1.join();
    thr2.join();
}
int main()
{
    Test1();
    Test2();
}


Test1:        
construct     
copy construct
construct     
operator=     
destroy
Hello
World
destroy
free resouce
destroy
free resouce
Test2:
construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
destroy
construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
copy construct
destroy
10
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
free resouce
10
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
free resouce

  shared_ptr在何種情況下乃至多線程的情況下都可以很好的管理和釋放資源。

shared_ptr的線程安全問題

  shared_ptr的線程安全要從兩方面說起。
  1、shared_ptr自身的線程安全。這裏的線程安全指的是指針內部的引用計數這個臨界資源的訪問的安全問題,爲了讓指針知道何時該釋放資源我們不得不在其中開闢一塊供多個shared_ptr訪問的引用計數,但如果在多線程中,就會發生線程安全問題。我們可以試着把上面模擬實現的代碼中的增加和減少引用計數部分的鎖刪除掉,然後利用多線程多創建幾個shared_ptr指向同一塊資源,就會發現如果引用計數的改變不是原子操作,就會發生多個線程同時更改引用計數但由於資源不同步導致部分更改丟失從而使得智能指針管理的資源要麼沒有釋放,要麼提前釋放,不過這點我們已經通過給臨界資源的訪問加鎖從而得以解決,因此我們可以說 shared_ptr自身是線程安全的
  2、shared_ptr管理的資源的線程安全問題。不得不說如果智能指針所管理的資源本身就是臨界資源的話,那麼所有的智能指針在對這塊資源的訪問上都是線程不安全的,就像我們使用普通的指針訪問臨界資源一樣,並不會因爲我們使用了智能指針而使得原本臨界資源的訪問就受到了保護。這裏的解決方法只能是從外部或者所管理的資源上添加訪問保護才能解決,這與智能指針本身無關。但也不得不提 shared_ptr所管理的資源是有線程安全問題存在的

shared_ptr的循環引用問題

  shared_ptr的循環引用問題是一類非常經典的問題,它通常會出現在shared_ptr的使用中,導致資源無法正確被釋放,看以下這個例子。

#include <iostream>
#include <memory>
//鏈表節點
struct ListNode
{
    //ListNode* _next;
    //ListNode* _prev;
    //統一交給智能指針管理,也方便後面鏈表節點之間互相連接
    std::shared_ptr<ListNode> _next;
    std::shared_ptr<ListNode> _prev;
    int _data;
    ~ListNode()
    {
        std::cout << "destroy" << std::endl;
    }
};
int main()
{
    //ListNode* node1 = new ListNode;
    //ListNode* node2 = new ListNode;
    //爲了防止拋異常無法釋放空間,將其交給智能指針管理
    std::shared_ptr<ListNode> node1(new ListNode);
    std::shared_ptr<ListNode> node2(new ListNode);
    node1->_next = node2;
    node2->_prev = node1;
    //異常拋出操作
    //...
    //delete node1;
    //delete node2;
    std::cout << "program will over" << std::endl;
}


program will over

  程序運行結束我們發現資源並沒有被釋放,爲什麼呢。

#include <iostream>
#include <memory>
//鏈表節點
struct ListNode
{
    //ListNode* _next;
    //ListNode* _prev;
    //統一交給智能指針管理,也方便後面鏈表節點之間互相連接
    std::shared_ptr<ListNode> _next;
    std::shared_ptr<ListNode> _prev;
    int _data;
    ~ListNode()
    {
        std::cout << "destroy" << std::endl;
    }
};
int main()
{
    //ListNode* node1 = new ListNode;
    //ListNode* node2 = new ListNode;
    //爲了防止拋異常無法釋放空間,將其交給智能指針管理
    std::shared_ptr<ListNode> node1(new ListNode);
    std::shared_ptr<ListNode> node2(new ListNode);
    //node1->_next = node2;
    //node2->_prev = node1;
    //異常拋出操作
    //...
    //delete node1;
    //delete node2;
    std::cout << "program will over" << std::endl;
}

program will over
destroy
destroy

  以上我們屏蔽掉了兩行互相指向,也就是鏈表節點連接起來的代碼,發現又可以正常釋放資源了,爲什麼呢?以上這種現象就是循環引用,接下來圖解以上過程說明爲什麼資源無法釋放以及如何造成了循環引用。
在這裏插入圖片描述
  循環引用的典型場景就是一個shared_ptr指向一塊內存空間A,空間A中還有一個shared_ptr指向另一塊空間B,而空間B也由一個shared_ptr管理並且其中又有一個shared_ptr指向空間A,這樣在釋放時就會發生循環引用的問題,導致空間無法釋放。那麼如何避免循環引用呢?
  造成循環引用的根結問題就是一個兩個使用shared_ptr互相指向的空間使得對方空間的引用計數多加了一次,才造成了循環引用,只要我們讓由shared_ptr管理的空間中的shared_ptr指向另一塊由shared_ptr管理的空間時另一塊空間的引用計數不增加就好了,簡單來說就是避免一次引用計數的增加,就可以避免卡死。但是shared_ptr指向一塊空間時此空間的引用計數必然會+1,這該怎麼辦呢?
  在標準庫中未我們提供了另一個智能指針weak_ptr,這個指針是專門提供給我們解決循環引用問題的,我們將代碼修改一下先看效果。

#include <iostream>
#include <memory>
//鏈表節點
struct ListNode
{
    //ListNode* _next;
    //ListNode* _prev;
    //統一交給智能指針管理,也方便後面鏈表節點之間互相連接
    //std::shared_ptr<ListNode> _next;
    //std::shared_ptr<ListNode> _prev;
    std::weak_ptr<ListNode> _next;
    std::weak_ptr<ListNode> _prev;
    int _data;
    ~ListNode()
    {
        std::cout << "destroy" << std::endl;
    }
};
int main()
{
    //ListNode* node1 = new ListNode;
    //ListNode* node2 = new ListNode;
    //爲了防止拋異常無法釋放空間,將其交給智能指針管理
    std::shared_ptr<ListNode> node1(new ListNode);
    std::shared_ptr<ListNode> node2(new ListNode);
    node1->_next = node2;
    node2->_prev = node1;
    //異常拋出操作
    //...
    //delete node1;
    //delete node2;
    std::cout << "program will over" << std::endl;
}


program will over
destroy
destroy

  資源完美的釋放了。那麼是如何解決的呢?
  首先可以將shared_ptr賦值給weak_ptr,因爲weak_ptr就是專門爲解決shared_ptr的問題產生的,並且weak_ptr指向某一shared_ptr管理的資源時它並不會增加該資源的引用計數,這完全符合我們解決循環引用的要求,避免了多增加的一次引用計數,於是問題解決了。

智能指針自定製deleter

  之前我們的智能指針都是在單一的管理一塊new出來的對象,那麼如下產生的空間智能指針是否能進行管理呢?

#include <iostream>
#include <memory>
#include <string>
int main()
{
    std::shared_ptr<std::string> sp1(new std::string[10]);
    std::shared_ptr<std::string> sp2((std::string*)malloc(sizeof(std::string)));
}

  智能指針無關版本默認釋放資源都會去delete資源,然而我們new[]malloc的資源必須通過delete[]free才能完全釋放或者不造成程序崩潰(malloc沒有對對象初始化,如果對象內含有指針,使用delete釋放資源調用析構函數可能會造成程序崩潰),因此我們想要正確釋放資源就需要正確的deleter,也就是釋放資源的方法,這樣的方法被稱爲*刪除器,當然智能指針也爲我們留下了這樣的接口,
  我們可以通過傳遞一個仿函數去更改默認的delete方法。

#include <iostream>
#include <memory>
#include <string>
//自定義刪除器
template<class T>
class FreeDeleter
{
public:
    void operator()(T* ptr)
    {
        free(ptr);
    }
};
template<class T>
class ArrayDeleter
{
public:
    void operator()(T* ptr)
    {
        delete[] ptr;
    }
};
int main()
{
    //ArrayDeleter<std::string> ad;
    std::shared_ptr<std::string> sp1(new std::string[10], ArrayDeleter<std::string>());
    //FreeDeleter<std::string> fd;
    std::shared_ptr<std::string> sp2((std::string*)malloc(sizeof(std::string)), FreeDeleter<std::string>());
}

  這樣就可以完成刪除器的自定義。那麼刪除器是如何作用於智能指針內的資源的呢?在在智能指針準備釋放資源時,他會調用仿函數刪除器來釋放資源,因此刪除器也是一個回調函數,通過對刪除器的自定義即可完成自定義釋放資源。

RAII的擴展

  RAII技術不光能使用在指針的資源管理上,一切需要我們手動釋放的資源都可以使用RAII管理,比如鎖。在庫中有一套量身設計的用RAII來管理鎖的類,叫做鎖守衛,它可以幫助我們在鎖超出作用域的時候自動解鎖,防止我們中間拋出異常卻沒有解鎖導致臨界資源再也無法訪問。
  模擬實現一個鎖守衛。

#include <iostream>
#include <mutex>
#include <thread>
template<class T>
class LockGuard
{
public:
    LockGuard(T& lock)
        :_lock(lock)
    {
        //std::cout << "lock" << std::endl;
        _lock.lock();
    }
    ~LockGuard()
    {
        //std::cout << "unlock" << std::endl;
        _lock.unlock();
    }
private:
    //禁用拷貝構造和賦值運算符重載
    LockGuard(const LockGuard& lck);
    LockGuard<T>& operator=(const LockGuard& lck);
    //這裏的鎖必須是引用形式的,因爲要所在同一個鎖上,不能拷貝
    T& _lock;
};
std::mutex mtx;
int num = 0;
void thr_start()
{
    //互斥鎖力度越大越好,消耗會小很多
    LockGuard<std::mutex> lck(mtx); //使用守衛鎖
    for(int i = 0; i < 1000000; i++)
    {
        ++num;
    }
}
int main()
{
    //開啓兩個線程分別加num一百萬次
    std::thread thr1(thr_start);
    std::thread thr2(thr_start);
    thr1.join();
    thr2.join();
    std::cout << "num:" << num << std::endl;
}


num:2000000

  庫中的鎖守衛。

#include <iostream>
#include <mutex>
#include <thread>
int main()
{
    //std::mutex mtx;
    //mtx.lock();
    ////...拋異常
    //mtx.unlock();
    //交給守衛鎖
    std::mutex mtx;
    std::lock_guard<std::mutex> guard(mtx);
}

  庫中還有另一個鎖守衛unique_lock,用法和lock_guard一致,那麼兩者有什麼區別呢?
  lock_guard只實現了RAII,因此加鎖和解鎖都由lock_guard控制,而unique_lock實現了更多的功能,它可以允許我們自己手動加鎖,也可以手動解鎖,也可以不阻塞地加鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章