<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      C++一些新的特性的理解

      一、智能指針

      為什么需要智能指針?

      智能指針主要解決一下問題:

      1. 內(nèi)存泄漏:內(nèi)存手動(dòng)釋放,使用智能指針可以自動(dòng)釋放
      2. 共享所有權(quán)的指針的傳播和釋放,比如多線程使用同一個(gè)對(duì)象時(shí)析構(gòu)的問題。

      C++里面的四個(gè)智能指針,auto_ptr,shared_ptr,unique_ptr,weak_ptr其中后3個(gè)是C++11支持,并且第一個(gè)已經(jīng)被C++棄用。

      幾個(gè)指針的特點(diǎn)

      • unique_ptr獨(dú)占對(duì)象的所有權(quán),由于沒有引用技術(shù),因此性能較好。
      • shared_ptr共享對(duì)象的所有權(quán),但性能略差。
      • weak_ptr配合shared_ptr,解決循環(huán)引用的問題。

      三者如何選擇呢?

      1.1 Shared_ptr內(nèi)存模型

      從圖中我們可以知道我們主要是保存一個(gè)原始的指針對(duì)象,還有個(gè)控制快,保存了shared_ptr的引用計(jì)數(shù)等。

      1.2智能指針使用場景案例

      1. 使用智能指針可以自己釋放占用的內(nèi)存
        例子:
      class A
      {
      public:
      	A();
      	~A();
      
      private:
      
      };
      
      A::A()
      {
      	std::cout << "A Created!" << std::endl;
      }
      
      A::~A()
      {
      	std::cout << "A Deleted!" << std::endl;
      }
      
      int main()
      {
      	auto Aptr = std::make_shared<A>();
      
      	auto Aptr1 = new A{};
      
      
      	return 0;
      }
      
      

      可以看到,智能指針會(huì)自動(dòng)釋放內(nèi)存

      2. 使用智能指針可以自己釋放占用的內(nèi)存

      1.3 shared_ptr共享的智能指針

      std::shared_ptr使用引用計(jì)數(shù)每一個(gè)shared_ptr的拷貝都指向相同的內(nèi)存,最后一個(gè)shared_ptr析構(gòu)的時(shí)候,內(nèi)存才會(huì)被釋放。
      shared_ptr共享被管理對(duì)象,同一時(shí)刻可以有多個(gè)shared_ptr擁有對(duì)象的所有權(quán),當(dāng)最后一個(gè)shared_ptr對(duì)象銷毀時(shí),被管理對(duì)象自動(dòng)銷毀。

      簡單來說,shared_ptr實(shí)現(xiàn)包含了兩部分,

      • 一個(gè)指向堆上創(chuàng)建的對(duì)象的裸指針,raw_ptr

      • 一個(gè)指向內(nèi)部隱藏的、共享的管理對(duì)象.share_count_object

      • user_count,當(dāng)前這個(gè)堆上對(duì)象被多少對(duì)象引用了,簡單來說就是引用計(jì)數(shù)。

      1.3.1 shared_ptr的基本用法和常用函數(shù)

      s.get(): 返回shared_ptr中保存的裸指針;
      s.reset():重置shared_ptr;

      • reset()不帶參數(shù)時(shí),若智能指針s是唯一指向該對(duì)象的指針,則釋放。并置空。若智能指針P不是唯一指向該對(duì)象的指針,則引用技術(shù)減少一,同時(shí)將P置空。
      • reset()帶參數(shù)時(shí),若智能指針s是唯一指向?qū)ο蟮闹羔槪瑒t釋放并指向新的對(duì)象。若P不是唯一的指針,則只減少引用技術(shù),并指向新的對(duì)象。如:
      auto ptr = std::make_shared<int>(303);
      ptr.reset(new int(10000));
      

      s.use_count();返回shared_ptr的強(qiáng)引用計(jì)數(shù)。
      s.unique();若use_count()為1.則返回true,否則返回false。

      1. 初始化make_shared/reset

      通過構(gòu)造函數(shù),std::make_shared_ptr輔助函數(shù)和reset方法來初始化shared_ptr,代碼如下:

      	std::shared_ptr<int>p1(new int(1));
      	std::shared_ptr<int>p2 = p1;
      	std::shared_ptr<int>p3;
      	p3.reset(new int(1));
      
      	if (p3)
      	{
      		std::cout << "p3 is not null!" << std::endl;
      	}
      
      

      我們應(yīng)該優(yōu)先使用make_shared來構(gòu)造智能指針,因?yàn)樗咝?/strong>

      auto sp1=make_shared<int>(100);
      //或
      shared_ptr<int>sql=make_shared<int>(100);
      
      //相當(dāng)于
      shared_ptr<int> sp1(new int(100))
      

      不能將一個(gè)原始指針直接賦值給一個(gè)智能指針,例如,下面這種方式錯(cuò)誤的:

      std::shared_ptr<int> p = new int(1);
      

      shared_ptr不能通過"直接將原始這種賦值"來初始化,需要通過構(gòu)造函數(shù)和輔助方法來初始化。

      • 對(duì)于一個(gè)未初始化的智能指針,可以通過reset方法來初始化;
      • 當(dāng)智能指針有值的時(shí)候調(diào)用reset會(huì)引起引用計(jì)數(shù)減1.

      另外智能指針可以通過重載的bool類型來操作符來判斷。

      	std::shared_ptr<int> p1;
      	p1.reset(new int(1));
      	std::shared_ptr<int>p2 = p1;
      
      	//引用次數(shù)此時(shí)應(yīng)該是2
      	std::cout << "p2.use_count() =" << p2.use_count() << std::endl;
      	p1.reset();
      	std::cout << "p1.reset()\n";
      	//引用次數(shù)此時(shí)應(yīng)該是1
      	std::cout << "p2.use_count() =" << p2.use_count() << std::endl;
      	if (!p1)
      	{
      		std::cout << "p1 is empty!\n";
      	}
      	if(!p2)
      	{
      		std::cout << "p2 is empty!\n";
      	}
      	p2.reset();
      	std::cout << "p2.reset()\n";
      	std::cout << "p2.use_count() =" << p2.use_count() << std::endl;
      
      	if (!p2)
      	{
      		std::cout << "p2 is empty!\n";
      	}
      
      
      2.獲取原始指針get

      當(dāng)需要獲取原始指針時(shí),可以通過get方法來返回原始指針,代碼如下所示:

      std::shared_ptr<int> ptr (new int(1));
      int *p=ptr.get();
      //不可以 delete p;
      

      謹(jǐn)慎使用p.get()的返回值,如果你不知道其危險(xiǎn)性則永遠(yuǎn)不要調(diào)用get()函數(shù)。
      p.get()的返回值就相當(dāng)于一個(gè)裸指針的值,不合適的使用這個(gè)值,上述陷阱的所有錯(cuò)誤都有可能發(fā)聲方法,遵守一下幾個(gè)約定:

      • 不要保存p.get()的返回值,無論是保存為裸指針還是shared_ptr都是錯(cuò)誤的。
      • 保存為裸指針不知道什么時(shí)候就會(huì)變成空懸指針,保存為shared_ptr則產(chǎn)生了獨(dú)立指針
      • 不要delete p.get()的返回值,會(huì)導(dǎo)致對(duì)一塊內(nèi)存delete兩次的錯(cuò)誤
      3. 指定刪除器

      如果用shared_pptr管理非new對(duì)象或是沒有析構(gòu)函數(shù)的類時(shí),應(yīng)當(dāng)為其傳遞合適的刪除器。

      示例代碼如下:

      void DeleteIntPtr(int *p)
      {
      	std::cout << "call DeleteIntPtr" << std::endl;
      	delete p;
      }
      int main()
      {
      
      	std::shared_ptr<int> p(new int(1), DeleteIntPtr);
      
      	return 0;
      }
      

      當(dāng)p的引用計(jì)數(shù)為0時(shí),自動(dòng)調(diào)用刪除器DeleteIntPtr來釋放對(duì)象的內(nèi)存,刪除器可以時(shí)一個(gè)lambda表達(dá)式,上面的寫法可以改為:

      std::shared_ptr<int> p(new int(1), [](int *p) {
      cout << "call lambda delete p" << endl;
      delete p;});
      
      

      當(dāng)我們用shared_ptr管理動(dòng)態(tài)數(shù)組時(shí),需要指定刪除器,因?yàn)閟hared_ptr的默認(rèn)刪除器不支持?jǐn)?shù)組對(duì)象,代碼如下所示::

      std::shared_ptr<int> p(new int[20], [](int* p) {delete[] p; });
      
      1.3.2 使用shared_ptr要注意的問題
      1. 不要用一個(gè)原始指針初始化多個(gè)shared_ptr

      例如下面錯(cuò)誤范例:

      int * ptr=new int;
      shared_ptr<int> p1(ptr);
      shared_ptr<int> p2(ptr); //邏輯錯(cuò)誤
      
      2. 不要在函數(shù)實(shí)參中創(chuàng)建shared_ptr

      對(duì)于下面的寫法:

      function(shared_ptr<int>(new int),g());
      
      

      因?yàn)镃++的函數(shù)參數(shù)的計(jì)算順序在不同的編譯器不同的約定下可能是不一樣的,一般是從右到左,但也可能從左到右,所以,可能的過程是先new int ,然后調(diào)用g(),如果恰好g()發(fā)生異常,而shared_ptr還沒有創(chuàng)建,則int內(nèi)存泄漏了,正確的寫法應(yīng)該是先創(chuàng)建智能指針,代碼如下:

      shared_ptr<int> p(new int);
      function(p,g());
      
      3.通過shared_from_this()返回this指針

      不要將this指針作為shared_ptr返回出來,因?yàn)閠his指針本質(zhì)上是一個(gè)裸指針,因此,這樣可能會(huì)到重復(fù)析構(gòu),看下面的例子。

      class A
      {
      public:
      	std::shared_ptr<A> GetSelf()
      	{
      		return std::shared_ptr<A>(this); // 不要這么做
      	}
      	~A()
      	{
      		std::cout << "Destructor A" << std::endl;
      	}
      };
      int main()
      {
      	std::shared_ptr<A> sp1(new A);
      	std::shared_ptr<A> sp2 = sp1->GetSelf();
      	return 0;
      }
      
      

      運(yùn)行后調(diào)用了兩次析構(gòu)函數(shù)。
      在這個(gè)例子中,由于用同一個(gè)指針(this)構(gòu)造了兩個(gè)智能指針sp1和sp2,而他們之間是沒有任何關(guān)系的,在離開作用域之后this將會(huì)被構(gòu)造的兩個(gè)智能各自析構(gòu),導(dǎo)致重復(fù)析構(gòu)的錯(cuò)誤

      正確返回this的shared_ptr的做法是:讓目標(biāo)類通過std::enable_shared_from_this類,然后使用積累的成員函數(shù)shared_from_this()來返回this的shared_ptr,如下所示:

      class A : public std::enable_shared_from_this<A>
      {
      public:
      	std::shared_ptr<A>GetSelf()
      	{
      		return shared_from_this(); //
      	}
      	~A()
      	{
      		std::cout << "Destructor A" << std::endl;
      	}
      };
      int main()
      {
      	std::shared_ptr<A> sp1(new A);
      	std::shared_ptr<A> sp2 = sp1->GetSelf(); // ok
      	return 0;
      }
      
      
      4.避免循環(huán)引用

      循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄漏,比如:

      class A;
      class B;
      class A {
      public:
      std::shared_ptr<B> bptr;
      ~A() {
      cout << "A is deleted" << endl;
      }
      };
      class B {
      public:
      std::shared_ptr<A> aptr;
      ~B() {
      cout << "B is deleted" << endl;
      }
      };
      int main()
      {
      {
      std::shared_ptr<A> ap(new A);
      std::shared_ptr<B> bp(new B);
      ap->bptr = bp;
      bp->aptr = ap;
      }
      cout<< "main leave" << endl; // 循環(huán)引用導(dǎo)致ap bp退出了作用域都沒有析構(gòu)
      return 0;
      }
      
      

      循環(huán)引用導(dǎo)致ap和bp的引用次數(shù)為2,在離開作用域以后,ap和BP的引用計(jì)數(shù)減為1,并不會(huì)減為0,導(dǎo)致兩個(gè)指針都不會(huì)被析構(gòu),產(chǎn)生內(nèi)存泄漏。
      解決的方法就是把A和B任何一個(gè)成員變量改為weak_ptr,具體方法見weak_ptr章節(jié)。

      1.4unique_ptr獨(dú)占的智能指針

      1. unique_ptr是一個(gè)獨(dú)占性的智能指針,不能將其賦值給另一個(gè)unique_ptr.
      2. unique_ptr可以指向一個(gè)數(shù)組
      3. unique_ptr需要確定刪除器的類型
         std::shared_ptr<int> ptr3(new int(1), [](int *p){delete  p;}); // 正確
         std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete  p;}); // 正確
      

      unique_ptr是一個(gè)獨(dú)占型的智能指針,它不允許其他的智能智能共享其內(nèi)部的指針,不允許通過賦值將一個(gè)unique_ptr賦值給另一個(gè)unique_ptr,下面的錯(cuò)誤的示例

      unique_ptr<T> my_ptr(new T);
      unique_ptr<T> my_other_ptr = my_ptr; // 報(bào)錯(cuò),不能復(fù)制
      

      unique_ptr不允許賦值,但可以通過函數(shù)返回給其他的unique_ptr,還可以通過std::move來轉(zhuǎn)移到其他的unique_ptr,這樣它本身就不再擁有原來指針的所有權(quán)了。例如

      unique_ptr<T> my_ptr(new T); // 正確
      unique_ptr<T> my_other_ptr = std::move(my_ptr); // 正確
      unique_ptr<T> ptr = my_ptr; // 報(bào)錯(cuò),不能復(fù)制
      

      std::make_shared是C++11的一部分,但std::make_unique不是。它實(shí)在c++14里加入標(biāo)準(zhǔn)庫的。、

      auto upw1(std::make_unique<Widget>()); // with make func
      std::unique_ptr<Widget> upw2(new Widget); // without make func
      

      使用new的版本重復(fù)了被創(chuàng)建對(duì)象的鍵入,但是make_unique函數(shù)則沒有。重復(fù)類型違背了軟件工程的
      一個(gè)重要原則:應(yīng)該避免代碼重復(fù),代碼中的重復(fù)會(huì)引起編譯次數(shù)增加,導(dǎo)致目標(biāo)代碼膨脹。

      除了unique_ptr的獨(dú)占性, unique_ptr和shared_ptr還有一些區(qū)別,比如

      • unique_ptr可以指向一個(gè)數(shù)組,代碼如下所示
      std::unique_ptr<int []> ptr(new int[10]);
      ptr[9] = 9;
      std::shared_ptr<int []> ptr2(new int[10]); // 這個(gè)是不合法的
      
      • unique_ptr指定刪除器和shared_ptr有區(qū)別
      std::shared_ptr<int> ptr3(new int(1), [](int *p){delete p;}); // 正確
      std::unique_ptr<int> ptr4(new int(1), [](int *p){delete p;}); // 錯(cuò)誤
      
      

      unique_ptr需要確定刪除器的類型,所以不能像shared_ptr那樣直接指定刪除器,可以這樣寫:

      std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;}); //
      正確
      

      關(guān)于shared_ptr和unique_ptr的使用場景是要根據(jù)實(shí)際應(yīng)用需求來選擇。
      如果希望只有一個(gè)智能指針管理資源或者管理數(shù)組就用unique_ptr,如果希望多個(gè)智能指針管理同一個(gè)
      資源就用shared_ptr。

      1.5 weak_ptr弱引用的智能指針

      什么是weak_ptr
      weak_ptr解決了什么問題;
      weak_ptr為什么能解決問題。

      share_ptr雖然已經(jīng)很好用了,但是有一點(diǎn)share_ptr智能指針還是有內(nèi)存泄露的情況,當(dāng)兩個(gè)對(duì)象相互
      使用一個(gè)shared_ptr成員變量指向?qū)Ψ剑瑫?huì)造成循環(huán)引用,使引用計(jì)數(shù)失效,從而導(dǎo)致內(nèi)存泄漏。

      weak_ptr 是一種不控制對(duì)象生命周期的智能指針, 它指向一個(gè) shared_ptr 管理的對(duì)象. 進(jìn)行該對(duì)象的內(nèi)
      存管理的是那個(gè)強(qiáng)引用的shared_ptr, weak_ptr只是提供了對(duì)管理對(duì)象的一個(gè)訪問手段。

      weak_ptr 設(shè)計(jì)的目的是為配合 shared_ptr 而引入的一種智能指針來協(xié)助 shared_ptr 工作, 它只可以從
      一個(gè) shared_ptr 或另一個(gè) weak_ptr 對(duì)象構(gòu)造, 它的構(gòu)造和析構(gòu)不會(huì)引起引用記數(shù)的增加或減少。

      1.5.1 weak_ptr的基本用法
      1. 通過use_count()方法獲取當(dāng)前觀察資源的引用計(jì)數(shù),如下所示:
      std::shared_ptr<int> sp(new int(10));
      std::weak_ptr<int> wp(sp);
      std::cout << wp.use_count() << std::endl; //結(jié)果講輸出1
      
      1. 通過expired()方法判斷所觀察資源是否已經(jīng)釋放,如下所示:
      std::shared_ptr<int> sp(new int(10));
      	std::weak_ptr<int> wp(sp);
      	if (wp.expired())
      		std::cout << "weak_ptr無效,資源已釋放";
      	else
      		std::cout << "weak_ptr有效";
      
      1. 通過lock方法獲取監(jiān)視的shared_ptr,如下所示:
        lock有什么用處?
      std::weak_ptr<int> gw;
      void f2()
      {
      	std::cout << "lock\n";
          auto spt = gw.lock();  // 鎖好資源再去判斷是否有效
          std::this_thread::sleep_for(std::chrono::seconds(2));
          if (gw.expired()) {
      	    std::cout << "gw Invalid, resource released\n";
          }
          else {
      	    std::cout << "gw Valid, *spt = " << *spt << std::endl;
          }
      }
      int main()
      {
          {
              auto sp = std::make_shared<int>(42);
              gw = sp;
              std::thread([&]() {
                  std::this_thread::sleep_for(std::chrono::seconds(1));
      
                  std::cout << "sp reset\n";
                  sp.reset();
                  }).detach();
      
                  f2();
          }
          f2();
          return 0;
      }
      
      
      1.5.2 weak_ptr返回this指針

      shared_ptr章節(jié)中提到不能直接將this指針返回shared_ptr,需要通過派生
      std::enable_shared_from_this類,并通過其方法shared_from_this來返回指針,原因是
      std::enable_shared_from_this類中有一個(gè)weak_ptr,這個(gè)weak_ptr用來觀察this智能指針,調(diào)用
      shared_from_this()方法是,會(huì)調(diào)用內(nèi)部這個(gè)weak_ptr的lock()方法,將所觀察的shared_ptr返回,再看
      前面的范例

      #include <iostream>
      #include <memory>
      using namespace std;
      class A: public std::enable_shared_from_this<A>
      {
      public:
      shared_ptr<A>GetSelf()
      {
      return shared_from_this(); //
      }
      ~A()
      {
      cout << "Destructor A" << endl;
      }
      };
      int main()
      {
      shared_ptr<A> sp1(new A);
      shared_ptr<A> sp2 = sp1->GetSelf(); // ok
      return 0;
      }
      

      輸出結(jié)果如下:
      Destructor A
      在外面創(chuàng)建A對(duì)象的智能指針和通過對(duì)象返回this的智能指針都是安全的,因?yàn)閟hared_from_this()是內(nèi)
      部的weak_ptr調(diào)用lock()方法之后返回的智能指針,在離開作用域之后,spy的引用計(jì)數(shù)減為0,A對(duì)象會(huì)
      被析構(gòu),不會(huì)出現(xiàn)A對(duì)象被析構(gòu)兩次的問題。
      需要注意的是,獲取自身智能指針的函數(shù)盡在shared_ptr的構(gòu)造函數(shù)被調(diào)用之后才能使用,因?yàn)?br> enable_shared_from_this內(nèi)部的weak_ptr只有通過shared_ptr才能構(gòu)造。

      1.5.3 weak_ptr解決循環(huán)引用問題

      在shared_ptr章節(jié)提到智能指針循環(huán)引用的問題,因?yàn)橹悄苤羔樀难h(huán)引用會(huì)導(dǎo)致內(nèi)存泄漏,可以通過
      weak_ptr解決該問題,只要將A或B的任意一個(gè)成員變量改為weak_ptr

      #include <iostream>
      #include <memory>
      using namespace std;
      class A;
      class B;
      class A {
      public:
      std::weak_ptr<B> bptr; // 修改為weak_ptr
      ~A() {
      cout << "A is deleted" << endl;
      }
      };
      class B {
      public:
      std::shared_ptr<A> aptr;
      ~B() {
      cout << "B is deleted" << endl;
      }
      };
      int main()
      {
      {
      std::shared_ptr<A> ap(new A);
      std::shared_ptr<B> bp(new B);
      ap->bptr = bp;
      bp->aptr = ap;
      }
      cout<< "main  leave" << endl;
      return 0;
      }
      

      這樣在對(duì)B的成員賦值時(shí),即執(zhí)行bp->aptr=ap;時(shí),由于aptr是weak_ptr,它并不會(huì)增加引用計(jì)數(shù),所
      以ap的引用計(jì)數(shù)仍然會(huì)是1,在離開作用域之后,ap的引用計(jì)數(shù)為減為0,A指針會(huì)被析構(gòu),析構(gòu)后其內(nèi)
      部的bptr的引用計(jì)數(shù)會(huì)被減為1,然后在離開作用域后bp引用計(jì)數(shù)又從1減為0,B對(duì)象也被析構(gòu),不會(huì)發(fā)
      生內(nèi)存泄漏。

      1.5.4 weak_ptr使用注意事項(xiàng)

      1. weak_ptr在使用前需要檢查合法性。

      weak_ptr<int> wp;
      {
      shared_ptr<int> sp(new int(1)); //sp.use_count()==1
      wp = sp; //wp不會(huì)改變引用計(jì)數(shù),所以sp.use_count()==1
      shared_ptr<int> sp_ok = wp.lock(); //wp沒有重載->操作符。只能這樣取所指向的對(duì)象
      }
      shared_ptr<int> sp_null = wp.lock(); //sp_null .use_count()==0
      

      因?yàn)樯鲜龃a中sp和sp_ok離開了作用域,其容納的K對(duì)象已經(jīng)被釋放了。
      得到了一個(gè)容納NULL指針的sp_null對(duì)象。在使用wp前需要調(diào)用wp.expired()函數(shù)判斷一下。
      因?yàn)閣p還仍舊存在,雖然引用計(jì)數(shù)等于0,仍有某處“全局”性的存儲(chǔ)塊保存著這個(gè)計(jì)數(shù)信息。直到最后
      一個(gè)weak_ptr對(duì)象被析構(gòu),這塊“堆”存儲(chǔ)塊才能被回收。否則weak_ptr無法直到自己所容納的那個(gè)指針
      資源的當(dāng)前狀態(tài)。

      如果shared_ptr sp_ok和weak_ptr wp;屬于同一個(gè)作用域呢?如下所示:

      weak_ptr<int> wp;
      shared_ptr<int> sp_ok;
      {
      shared_ptr<int> sp(new int(1)); //sp.use_count()==1
      wp = sp; //wp不會(huì)改變引用計(jì)數(shù),所以sp.use_count()==1
      sp_ok = wp.lock(); //wp沒有重載->操作符。只能這樣取所指向的對(duì)象
      }
      if(wp.expired()) {
      cout << "shared_ptr is destroy" << endl;
      } else {
      cout << "shared_ptr no destroy" << endl;
      }
      
      

      1.6 智能指針安全性問題

      引用計(jì)數(shù)本身是安全的,至于智能指針是否安全需要結(jié)合實(shí)際使用分情況討論:
      情況1:多線程代碼操作的是同一個(gè)shared_ptr的對(duì)象,此時(shí)是不安全的。
      比如std::thread的回調(diào)函數(shù),是一個(gè)lambda表達(dá)式,其中引用捕獲了一個(gè)shared_ptr.

      std::thread td([&sp1]()){....});
      
      

      又或者通過回調(diào)函數(shù)的參數(shù)傳入的shared_ptr對(duì)象,參數(shù)類型引用

      void fn(shared_ptr<A>&sp) {
      ...
      }
      ..
      std::thread td(fn, sp1);
      

      這時(shí)候必然不是線程安全的。
      情況2:多線程代碼操作的不是同一個(gè)shared_ptr的對(duì)象
      這里指的是管理的數(shù)據(jù)是同一份,而shared_ptr不是同一個(gè)對(duì)象。比如多線程回調(diào)的lambda的是按值捕
      獲的對(duì)象。

      std::thread td([sp1]()){....});
      

      另個(gè)線程傳遞的shared_ptr是值傳遞,而非引用:

      void fn(shared_ptr<A>sp) {
      ...
      }
      ..
      std::thread td(fn, sp1);
      

      這時(shí)候每個(gè)線程內(nèi)看到的sp,他們所管理的是同一份數(shù)據(jù),用的是同一個(gè)引用計(jì)數(shù)。但是各自是不同的
      對(duì)象,當(dāng)發(fā)生多線程中修改sp指向的操作的時(shí)候,是不會(huì)出現(xiàn)非預(yù)期的異常行為的。
      也就是說,如下操作是安全的。

      void fn(shared_ptr<A>sp) {
      ...
      if(..){
      sp = other_sp;
      } else {
      sp = other_sp2;
      }
      }
      
      

      需要注意:所管理數(shù)據(jù)的線程安全性問題。顯而易見,所管理的對(duì)象必然不是線程安全的,必然 sp1、
      sp2、sp3智能指針實(shí)際都是指向?qū)ο驛, 三個(gè)線程同時(shí)操作對(duì)象A,那對(duì)象的數(shù)據(jù)安全必然是需要對(duì)象
      A自己去保證。

      2.什么是左值引用、右值引用

      引用本質(zhì)是別名,可以通過引用修改變量的值,傳參時(shí)傳引用可以避免拷貝。

      2.1 左值引用

      左值引用:能指向左值,不能指向右值的就是左值引用
      但是,const左值引用是可以指向右值的:

      const int &ref_a = 5; // 編譯通過
      

      const左值引用不會(huì)修改指向值,值,因此可以指向右值,這也是為什么要使用 const & 作為函數(shù)參數(shù)的原因之一,如 std::vectorpush_back

      void push_back (const value_type& val);
      

      如果沒有const,vec.push_back(5)這樣的代碼就無法編譯通過。

      2.2 右值引用

      再看下右值引用,右值引用的標(biāo)志是 && ,顧名思義,右值引用專門為右值而生,可以指向右值,不能指
      向左值

      int &&ref_a_right = 5; // ok
      int a = 5;
      int &&ref_a_left = a; // 編譯不過,右值引用不可以指向左值
      ref_a_right = 6; // 右值引用的用途:可以修改右值
      

      2.3對(duì)左右值引用本質(zhì)的討論

      我們必須先掌握這個(gè)概念——值類別。
      值類別是 C++11 標(biāo)準(zhǔn)中新引入的概念,具體來說它是表達(dá)式的一種屬性,該屬性
      將表達(dá)式分為 3 個(gè)類別,它們分別是左值(lvalue)、純右值(prvalue)和將亡值
      (xvalue),如圖 6-1 所示。從前面的內(nèi)容中我們知道早在 C++98 的時(shí)候,已經(jīng)有了
      一些關(guān)于左值和右值的概念了,只不過當(dāng)時(shí)這些概念對(duì)于 C++程序編寫并不重要。
      但是由于 C++11 中右值引用的出現(xiàn),值類別被賦予了全新的含義。可惜的是,在
      C++11 標(biāo)準(zhǔn)中并沒能夠清晰地定義它們,比如在 C++11 的標(biāo)準(zhǔn)文檔中,左值的概
      念只有一句話:“指定一個(gè)函數(shù)或一個(gè)對(duì)象”,這樣的描述顯然是不清晰的。這種
      糟糕的情況一直延續(xù)到 C++17 標(biāo)準(zhǔn)的推出才得到解決。所以現(xiàn)在是時(shí)候讓我們重
      新認(rèn)識(shí)這些概念了。

      表達(dá)式首先被分為了泛左值(glvalue)和右值(rvalue),其中泛左值被進(jìn)一步劃
      分為左值和將亡值,右值又被劃分為將亡值和純右值。理解這些概念的關(guān)鍵在于泛
      左值、純右值和將亡值。
      1.所謂泛左值是指一個(gè)通過評(píng)估能夠確定對(duì)象、位域或函數(shù)的標(biāo)識(shí)的表達(dá)式。
      簡單來說,它確定了對(duì)象或者函數(shù)的標(biāo)識(shí)(具名對(duì)象)。
      2.而純右值是指一個(gè)通過評(píng)估能夠用于初始化對(duì)象和位域,或者能夠計(jì)算運(yùn)算
      符操作數(shù)的值的表達(dá)式。
      3.將亡值屬于泛左值的一種,它表示資源可以被重用的對(duì)象和位域,通常這是
      因?yàn)樗鼈兘咏渖芷诘哪┪玻硗庖部赡苁墙?jīng)過右值引用的轉(zhuǎn)換產(chǎn)生的。

      2.3.1 右值引用有辦法指向左值嗎?

      有辦法,使用 std::move

      nt a = 5; // a是個(gè)左值
      int &ref_a_left = a; // 左值引用指向左值
      int &&ref_a_right = std::move(a); // 通過std::move將左值轉(zhuǎn)化為右值,可以被右值引用指向
      cout << a; // 打印結(jié)果:5
      

      在上邊的代碼里,看上去是左值a通過std::move移動(dòng)到了右值ref_a_right中,那是不是a里邊就沒有值
      了?并不是,打印出a的值仍然是5。
      std::move是一個(gè)非常有迷惑性的函數(shù):

      • 不理解左右值概念的人們往往以為它能把一個(gè)變量里的內(nèi)容移動(dòng)到另一個(gè)變量;
      • 但事實(shí)上std::move移動(dòng)不了什么,唯一的功能是把左值強(qiáng)制轉(zhuǎn)化為右值,讓右值引用可以指向左值。其實(shí)現(xiàn)等同于一個(gè)類型轉(zhuǎn)換: static_cast<T&&>(lvalue) 。 所以,單純的std::move(xxx)
        不會(huì)有性能提升。

      同樣的,**右值引用能指向右值****,本質(zhì)上也是把右值提升為一個(gè)左值,并定義一個(gè)右值引用通過std::move
      指向該左值:

      int &&ref_a = 5;
      ref_a = 6;
      等同于以下代碼:
      int temp = 5;
      int &&ref_a = std::move(temp);
      ref_a = 6;
      // 此時(shí)temp等于?
      

      2.3.2 左值引用、右值引用本身是左值還是右值?

      被聲明出來的左、右值引用都是左值.因?yàn)楸宦暶鞒龅淖笥抑狄檬?strong>有地址的,也位于等號(hào)左邊。仔細(xì)
      看下邊代碼:

      // 形參是個(gè)右值引用
      void change(int&& right_value) {
      right_value = 8;
      }
      int main() {
      int a = 5; // a是個(gè)左值
      int &ref_a_left = a; // ref_a_left是個(gè)左值引用
      int &&ref_a_right = std::move(a); // ref_a_right是個(gè)右值引用
      change(a); // 編譯不過,a是左值,change參數(shù)要求右值
      change(ref_a_left); // 編譯不過,左值引用ref_a_left本身也是個(gè)左值
      change(ref_a_right); // 編譯不過,右值引用ref_a_right本身也是個(gè)左值
      change(std::move(a)); // 編譯通過
      change(std::move(ref_a_right)); // 編譯通過
      change(std::move(ref_a_left)); // 編譯通過
      change(5); // 當(dāng)然可以直接接右值,編譯通過
      cout << &a << ' ';
      cout << &ref_a_left << ' ';
      cout << &ref_a_right;
      // 打印這三個(gè)左值的地址,都是一樣的
      }
      

      看完后你可能有個(gè)問題,std::move會(huì)返回一個(gè)右值引用int &&,它是左值還是右值呢? 從表達(dá)式int &&ref = std::move(a)來看,右值引用ref指向的必須是右值,所以move返回的int &&是個(gè)右值。
      所以右值引用既可能是左值,又可能是右值嗎? 確實(shí)如此:右值引用既可以是左值也可以是右值,如果
      有名稱則為左值,否則是右值。

      或者說:作為函數(shù)返回值的&&是右值,直接聲明出來的&&是左值.這同樣也符合前面章節(jié)對(duì)左值,
      右值的判定方式:其實(shí)引用和普通變量是一樣的, int &&ref = std::move(a) 和 int a = 5 沒有什
      么區(qū)別,等號(hào)左邊就是左值,右邊就是右值。
      最后,從上述分析中我們得到如下結(jié)論:

      1. 從性能上講,左右值引用沒有區(qū)別,傳參使用左右值引用都可以避免拷貝。
      2. 右值引用可以直接指向右值,也可以通過std::move指向左值;而左值引用只能指向左值(const左
        值引用也能指向右值)。
      3. 作為函數(shù)形參時(shí),右值引用更靈活。雖然const左值引用也可以做到左右值都接受,但它無法修
        改,有一定局限性。
      void f(const int& n) {
      n += 1; // 編譯失敗,const左值引用不能修改指向變量
      }
      void f2(int && n) {
      n += 1; // ok
      }
      int main() {
      f(5);
      f2(5);
      }
      

      3 右值引用和std::move使用場景

      std::move只是類型轉(zhuǎn)換工具,不會(huì)對(duì)性能有好處;
      右值引用在作為函數(shù)形參時(shí)更具靈活性。他們有什么實(shí)際應(yīng)用場景嗎?

      3.1 右值引用優(yōu)化性能,避免深拷貝

      淺拷貝重復(fù)釋放

      對(duì)于含有堆內(nèi)存的類,我們需要提供深拷貝的拷貝構(gòu)造函數(shù),如果使用默認(rèn)構(gòu)造函數(shù),會(huì)導(dǎo)致堆內(nèi)存的
      重復(fù)刪除,比如下面的代碼:

      #include <iostream>
      using namespace std;
      class A
      {
      public:
      A() :m_ptr(new int(0)) {
      cout << "constructor A" << endl;
      }
      ~A(){
      cout << "destructor A, m_ptr:" << m_ptr << endl;
      delete m_ptr;
      m_ptr = nullptr;
      }
      private:
      int* m_ptr;
      };
      // 為了避免返回值優(yōu)化,此函數(shù)故意這樣寫
      A Get(bool flag)
      {
      A a;
      A b;
      cout << "ready return" << endl;
      if (flag)
      return a;
      else
      return b;
      }
      int main()
      {
      {
      A a = Get(false); // 運(yùn)行報(bào)錯(cuò)
      }
      cout << "main finish" << endl;
      return 0;
      }
      
      

      打印
      constructor A
      constructor A
      ready return
      destructor A, m_ptr:000002628A9323D0
      destructor A, m_ptr:000002628A92A0B0
      destructor A, m_ptr:000002628A9323D0

      深拷貝構(gòu)造函數(shù)

      在上面的代碼中,默認(rèn)構(gòu)造函數(shù)是淺拷貝,main函數(shù)的 a 和Get函數(shù)的 b 會(huì)指向同一個(gè)指針 m_ptr,在
      析構(gòu)的時(shí)候會(huì)導(dǎo)致重復(fù)刪除該指針。正確的做法是提供深拷貝的拷貝構(gòu)造函數(shù),比如下面的代碼:

      #include <iostream>
      using namespace std;
      class A
      {
      public:
      A() :m_ptr(new int(0)) {
      cout << "constructor A" << endl;
      }
      A(const A& a) :m_ptr(new int(*a.m_ptr)) {
      cout << "copy constructor A" << endl;
      }
      ~A(){
      cout << "destructor A, m_ptr:" << m_ptr << endl;
      delete m_ptr;
      m_ptr = nullptr;
      }
      private:
      int* m_ptr;
      };
      // 為了避免返回值優(yōu)化,此函數(shù)故意這樣寫
      A Get(bool flag)
      {
      A a;
      A b;
      cout << "ready return" << endl;
      if (flag)
      return a;
      else
      return b;
      }
      int main()
      {
      {
      A a = Get(false); // 正確運(yùn)行
      }
      cout << "main finish" << endl;
      return 0;
      }
      
      

      constructor A
      constructor A
      ready return
      copy constructor A
      destructor A, m_ptr:000002455ECEA0B0
      destructor A, m_ptr:000002455ECEA070
      destructor A, m_ptr:000002455ECEA0F0
      main finish

      移動(dòng)構(gòu)造函數(shù)

      這樣就可以保證拷貝構(gòu)造時(shí)的安全性,但有時(shí)這種拷貝構(gòu)造卻是不必要的,比如上面代碼中的拷貝構(gòu)造
      就是不必要的。上面代碼中的 Get 函數(shù)會(huì)返回臨時(shí)變量,然后通過這個(gè)臨時(shí)變量拷貝構(gòu)造了一個(gè)新的對(duì)
      象 b,臨時(shí)變量在拷貝構(gòu)造完成之后就銷毀了,如果堆內(nèi)存很大,那么,這個(gè)拷貝構(gòu)造的代價(jià)會(huì)很大,
      帶來了額外的性能損耗
      。有沒有辦法避免臨時(shí)對(duì)象的拷貝構(gòu)造呢?答案是肯定的。看下面的代碼:

      #include <iostream>
      using namespace std;
      class A
      {
      public:
      A() :m_ptr(new int(0)) {
      cout << "constructor A" << endl;
      }
      A(const A& a) :m_ptr(new int(*a.m_ptr)) {
      cout << "copy constructor A" << endl;
      }
      // 移動(dòng)構(gòu)造函數(shù),可以淺拷貝
      A(A&& a) :m_ptr(a.m_ptr) {
      a.m_ptr = nullptr; // 為防止a析構(gòu)時(shí)delete data,提前置空其m_ptr
      cout << "move constructor A" << endl;
      }
      ~A(){
      cout << "destructor A, m_ptr:" << m_ptr << endl;
      if(m_ptr)
      delete m_ptr;
      }
      private:
      int* m_ptr;
      };
      // 為了避免返回值優(yōu)化,此函數(shù)故意這樣寫
      A Get(bool flag)
      {
      A a;
      A b;
      cout << "ready return" << endl;
      if (flag)
      return a;
      else
      return b;
      }
      int main()
      {
      {
      A a = Get(false); // 正確運(yùn)行
      }
      cout << "main finish" << endl;
      return 0;
      }
      
      

      constructor A
      constructor A
      ready return
      move constructor A
      destructor A, m_ptr:0000000000000000
      destructor A, m_ptr:000002640DF0A030
      destructor A, m_ptr:000002640DF0A070
      main finish

      上面的代碼中沒有了拷貝構(gòu)造,取而代之的是移動(dòng)構(gòu)造( Move Construct)。從移動(dòng)構(gòu)造函數(shù)的實(shí)現(xiàn)
      中可以看到,它的參數(shù)是一個(gè)右值引用類型的參數(shù) A&&,這里沒有深拷貝,只有淺拷貝,這樣就避免了
      對(duì)臨時(shí)對(duì)象的深拷貝,提高了性能。這里的 A&& 用來根據(jù)參數(shù)是左值還是右值來建立分支,如果是臨時(shí)
      值,則會(huì)選擇移動(dòng)構(gòu)造函數(shù)。移動(dòng)構(gòu)造函數(shù)只是將臨時(shí)對(duì)象的資源做了淺拷貝,不需要對(duì)其進(jìn)行深拷
      貝,從而避免了額外的拷貝,提高性能。這也就是所謂的移動(dòng)語義( move 語義),右值引用的一個(gè)重
      要目的是用來支持移動(dòng)語義的。

      移動(dòng)語義可以將資源(堆、系統(tǒng)對(duì)象等)通過淺拷貝方式從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象,這樣能夠減少
      不必要的臨時(shí)對(duì)象的創(chuàng)建、拷貝以及銷毀
      ,可以大幅度提高 C++ 應(yīng)用程序的性能,消除臨時(shí)對(duì)象的維護(hù)
      (創(chuàng)建和銷毀)對(duì)性能的影響。

      3.2 移動(dòng)(move )語義

      move是將對(duì)象的狀態(tài)或者所有權(quán)從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象,只是轉(zhuǎn)義,沒有內(nèi)存拷貝。要move語
      義起作用,核心在于需要對(duì)應(yīng)類型的構(gòu)造函數(shù)支持。

      //2-3-2-move
      #include <iostream>
      #include <vector>
      #include <cstdio>
      #include <cstdlib>
      #include <string.h>
      using namespace std;
      
      class MyString {
      private:
          char* m_data;
          size_t   m_len;
          void copy_data(const char *s) {
              m_data = new char[m_len+1];
              memcpy(m_data, s, m_len);
              m_data[m_len] = '\0';
          }
      public:
          MyString() {
              m_data = NULL;
              m_len = 0;
          }
      
          MyString(const char* p) {
              m_len = strlen (p);
              copy_data(p);
          }
      
          MyString(const MyString& str) {
              m_len = str.m_len;
              copy_data(str.m_data);
              std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl;
          }
          MyString& operator=(const MyString& str) {
              if (this != &str) {
                  m_len = str.m_len;
                  copy_data(str.m_data);
              }
              std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl;
              return *this;
          }
      
          // 用c++11的右值引用來定義這兩個(gè)函數(shù)
          MyString(MyString&& str) {
              std::cout << "Move Constructor is called! source: " << str.m_data << std::endl;
              m_len = str.m_len;
              m_data = str.m_data; //避免了不必要的拷貝
              str.m_len = 0;
              str.m_data = NULL;
          }
      
          MyString& operator=(MyString&& str) {
              std::cout << "Move Assignment is called! source: " << str.m_data << std::endl;
              if (this != &str) {
                  m_len = str.m_len;
                  m_data = str.m_data; //避免了不必要的拷貝
                  str.m_len = 0;
                  str.m_data = NULL;
              }
              return *this;
          }
      
          virtual ~MyString() {
              if (m_data) free(m_data);
          }
      };
      
      
      int main()
      {
          MyString a;
          a = MyString("Hello");      // Move Assignment
          MyString b = a;             // Copy Constructor
          MyString c = std::move(a);  // Move Constructor is called! 將左值轉(zhuǎn)為右值
      
          std::vector<MyString> vec;
          vec.push_back(MyString("World")); // Move Constructor is called!
          return 0;
      }
      
      

      有了右值引用和轉(zhuǎn)移語義,我們?cè)谠O(shè)計(jì)和實(shí)現(xiàn)類時(shí),對(duì)于需要?jiǎng)討B(tài)申請(qǐng)大量資源的類,應(yīng)該設(shè)計(jì)右值引
      用的拷貝構(gòu)造函數(shù)和賦值函數(shù),以提高應(yīng)用程序的效率

      3.3 forward 完美轉(zhuǎn)發(fā)

      forward 完美轉(zhuǎn)發(fā)實(shí)現(xiàn)了參數(shù)在傳遞過程中保持其值屬性的功能,即若是左值,則傳遞之后仍然是左
      值,若是右值,則傳遞之后仍然是右值。
      現(xiàn)存在一個(gè)函數(shù)

      Template<class T>
      void func(T &&val);
      
      

      根據(jù)前面所描述的,這種引用類型既可以對(duì)左值引用,亦可以對(duì)右值引用。
      但要注意,引用以后,這個(gè)val值它本質(zhì)上是一個(gè)左值!
      看下面例子

      int &&a = 10;
      int &&b = a; //錯(cuò)誤
      

      注意這里,a是一個(gè)右值引用,但其本身a也有內(nèi)存名字,所以a本身是一個(gè)左值,再用右值引用引用a這
      是不對(duì)的。
      因此我們有了std::forward()完美轉(zhuǎn)發(fā),這種T &&val中的val是左值,但如果我們用std::forward (val),
      就會(huì)按照參數(shù)原來的類型轉(zhuǎn)發(fā);

      int &&a = 10;
      int &&b = std::forward<int>(a);
      
      

      這樣是正確的!
      通過范例鞏固下知識(shí):

      //2-3-3-forward1
      #include <iostream>
      using namespace std;
      template <class T>
      void Print(T &t)
      {
      cout << "L" << t << endl;
      }
      template <class T>
      void Print(T &&t)
      {
      cout << "R" << t << endl;
      }
      template <class T>
      void func(T &&t)
      {
      Print(t);
      Print(std::move(t));
      Print(std::forward<T>(t));
      }
      int main()
      {
      cout << "-- func(1)" << endl;
      func(1);
      int x = 10;
      int y = 20;
      cout << "-- func(x)" << endl;
      func(x); // x本身是左值
      cout << "-- func(std::forward<int>(y))" << endl;
      func(std::forward<int>(y)); //
      return 0;
      }
      

      -- func(1)
      L1
      R1
      R1
      -- func(x)
      L10
      R10
      L10
      -- func(std::forward(y))
      L20
      R20
      R20

      解釋:
      func(1) :由于1是右值,所以未定的引用類型T&&v被一個(gè)右值初始化后變成了一個(gè)右值引用,但是在
      func()函數(shù)體內(nèi)部,調(diào)用PrintT(v) 時(shí),v又變成了一個(gè)左值(因?yàn)樵趕td::forward里它已經(jīng)變成了一個(gè)具
      名的變量,所以它是一個(gè)左值),因此,示例測試結(jié)果第一個(gè)PrintT被調(diào)用,打印出“L1"
      調(diào)用PrintT(std::forward(v))時(shí),由于std::forward會(huì)按參數(shù)原來的類型轉(zhuǎn)發(fā),因此,它還是一個(gè)右值
      (這里已經(jīng)發(fā)生了類型推導(dǎo),所以這里的T&&不是一個(gè)未定的引用類型,會(huì)調(diào)用void PrintT(T&&t)函
      數(shù)打印 “R1”.調(diào)用PrintT(std::move(v))是將v變成一個(gè)右值(v本身也是右值),因此,它將輸出”R1"
      func(x)未定的引用類型T&&v被一個(gè)左值初始化后變成了一個(gè)左值引用,因此,在調(diào)用
      PrintT(std::forward(v))時(shí)它會(huì)被轉(zhuǎn)發(fā)到void PrintT(T&t).
      forward將左值轉(zhuǎn)換為右值:

      MyString str1 = "hello";
      MyString str2(str1);
      MyString str3 = Fun();
      MyString str4 = move(str2);
      MyString str5(forward<MyString>(str3));
      

      綜合示例

      //2-3-3-forward2
      #include "stdio.h"
      #include <iostream>
      #include <cstring>
      #include <vector>
      using namespace std;
      class A
      {
      public:
          A() : m_ptr(NULL), m_nSize(0) {}
          A(int *ptr, int nSize)
          {
              m_nSize = nSize;
              m_ptr = new int[nSize];
              printf("A(int *ptr, int nSize) m_ptr:%p\n", m_ptr);
              if (m_ptr)
              {
                  memcpy(m_ptr, ptr, sizeof(sizeof(int) * nSize));
              }
          }
          A(const A &other) // 拷貝構(gòu)造函數(shù)實(shí)現(xiàn)深拷貝
          {
              m_nSize = other.m_nSize;
              if (other.m_ptr)
              {
                  printf("A(const A &other) m_ptr:%p\n", m_ptr);
                  if(m_ptr)
                      delete[] m_ptr;
                  printf("delete[] m_ptr\n");
                  m_ptr = new int[m_nSize];
                  memcpy(m_ptr, other.m_ptr, sizeof(sizeof(int) * m_nSize));
              }
              else
              {
                  if(m_ptr)
                      delete[] m_ptr;
                  m_ptr = NULL;
              }
              cout << "A(const int &i)" << endl;
          }
          // 右值引用移動(dòng)構(gòu)造函數(shù)
          A(A &&other)
          {
              m_ptr = NULL;
              m_nSize = other.m_nSize;
              if (other.m_ptr)
              {
                  m_ptr = move(other.m_ptr); // 移動(dòng)語義
                  other.m_ptr = NULL;
              }
          }
          ~A()
          {
              if (m_ptr)
              {
                  delete[] m_ptr;
                  m_ptr = NULL;
              }
          }
          void deleteptr()
          {
              if (m_ptr)
              {
                  delete[] m_ptr;
                  m_ptr = NULL;
              }
          }
          int *m_ptr = NULL;  // 增加初始化
          int m_nSize = 0;
      };
      int main()
      {
          int arr[] = {1, 2, 3};
          A a(arr, sizeof(arr) / sizeof(arr[0]));
          cout << "m_ptr in a Addr: 0x" << a.m_ptr << endl;
          A b(a);
          cout << "m_ptr in b Addr: 0x" << b.m_ptr << endl;
      
          b.deleteptr();
          A c(std::forward<A>(a)); // 完美轉(zhuǎn)換
          cout << "m_ptr in c Addr: 0x" << c.m_ptr << endl;
          c.deleteptr();
          vector<int> vect{1, 2, 3, 4, 5};
          cout << "before move vect size: " << vect.size() << endl;
          vector<int> vect1 = move(vect);
          cout << "after move vect size: " << vect.size() << endl;
          cout << "new vect1 size: " << vect1.size() << endl;
          return 0;
      }
      
      

      3.4 emplace_back 減少內(nèi)存拷貝和移動(dòng)

      對(duì)于STL容器,C++11后引入了emplace_back接口。
      emplace_back是就地構(gòu)造,不用構(gòu)造后再次復(fù)制到容器中。因此效率更高。
      考慮這樣的語句:

      vector<string> testVec;
      testVec.push_back(string(16, 'a'));
      

      上述語句足夠簡單易懂,將一個(gè)string對(duì)象添加到testVec中。底層實(shí)現(xiàn):

      • 首先,string(16, ‘a(chǎn)’)會(huì)創(chuàng)建一個(gè)string類型的臨時(shí)對(duì)象,這涉及到一次string構(gòu)造過程。
      • 其次,vector內(nèi)會(huì)創(chuàng)建一個(gè)新的string對(duì)象,這是第二次構(gòu)造。
      • 最后在push_back結(jié)束時(shí),最開始的臨時(shí)對(duì)象會(huì)被析構(gòu)。加在一起,這兩行代碼會(huì)涉及到兩次
        string構(gòu)造和一次析構(gòu)。
        c++11可以用emplace_back代替push_back,emplace_back可以直接在vector中構(gòu)建一個(gè)對(duì)象,而非
        創(chuàng)建一個(gè)臨時(shí)對(duì)象,再放進(jìn)vector,再銷毀。emplace_back可以省略一次構(gòu)建和一次析構(gòu),從而達(dá)到優(yōu)
        化的目的。
      //2-5-emplace_back
      #include <vector>
      #include <string>
      #include "time_interval.h"
      
      int main() {
      
      
          std::vector<std::string> v;
          int count = 10000000;
          v.reserve(count);       //預(yù)分配十萬大小,排除掉分配內(nèi)存的時(shí)間
          {
              TIME_INTERVAL_SCOPE("push_back string:");
              for (int i = 0; i < count; i++)
              {
                  std::string temp("ceshi");
                  v.push_back(temp);// push_back(const string&),參數(shù)是左值引用
              }
          }
      
          v.clear();
          {
              TIME_INTERVAL_SCOPE("push_back move(string):");
              for (int i = 0; i < count; i++)
              {
                  std::string temp("ceshi");
                  v.push_back(std::move(temp));// push_back(string &&), 參數(shù)是右值引用
              }
          }
      
          v.clear();
          {
              TIME_INTERVAL_SCOPE("push_back(string):");
              for (int i = 0; i < count; i++)
              {
                  v.push_back(std::string("ceshi"));// push_back(string &&), 參數(shù)是右值引用
              }
          }
      
          v.clear();
          {
              TIME_INTERVAL_SCOPE("push_back(c string):");
              for (int i = 0; i < count; i++)
              {
                  v.push_back("ceshi");// push_back(string &&), 參數(shù)是右值引用
              }
          }
      
          v.clear();
          {
              TIME_INTERVAL_SCOPE("emplace_back(c string):");
              for (int i = 0; i < count; i++)
              {
                  v.emplace_back("ceshi");// 只有一次構(gòu)造函數(shù),不調(diào)用拷貝構(gòu)造函數(shù),速度最快
              }
          }
      }
      
      
      

      結(jié)果

      push_back string:9439 ms
      push_back move(string):8566 ms
      push_back(string):7990 ms
      push_back(c string):8427 ms
      emplace_back(c string):3581 ms
      

      3.5 小結(jié)

      C++11 在性能上做了很大的改進(jìn),最大程度減少了內(nèi)存移動(dòng)和復(fù)制,通過右值引用、 forward、
      emplace 和一些無序容器我們可以大幅度改進(jìn)程序性能。

      • 右值引用僅僅是通過改變資源的所有者來避免內(nèi)存的拷貝,能大幅度提高性能。
      • forward 能根據(jù)參數(shù)的實(shí)際類型轉(zhuǎn)發(fā)給正確的函數(shù)。
      • emplace 系列函數(shù)通過直接構(gòu)造對(duì)象的方式避免了內(nèi)存的拷貝和移動(dòng)。

      四 匿名函數(shù)lambda

      重點(diǎn):

      • 怎么傳遞參數(shù)
      • 傳引用還是傳值

      4.1匿名函數(shù)的基本語法

      [捕獲列表](參數(shù)列表) mutable(可選) 異常屬性 -> 返回類型 {
      // 函數(shù)體
      }
      

      語法規(guī)則:lambda表達(dá)式可以看成是一般函數(shù)的函數(shù)名被略去,返回值使用了一個(gè) -> 的形式表示。唯
      一與普通函數(shù)不同的是增加了“捕獲列表”。

      //[捕獲列表](參數(shù)列表)->返回類型{函數(shù)體}
      int main()
      {
      auto Add = [](int a, int b)->int {
      return a + b;
      };
      std::cout << Add(1, 2) << std::endl; //輸出3
      return 0;
      }
      

      一般情況下,編譯器可以自動(dòng)推斷出lambda表達(dá)式的返回類型,所以我們可以不指定返回類型,即:

      //[捕獲列表](參數(shù)列表){函數(shù)體}
      int main()
      {
      auto Add = [](int a, int b) {
      return a + b;
      };
      std::cout << Add(1, 2) << std::endl; //輸出3
      return 0;
      }
      
      

      但是如果函數(shù)體內(nèi)有多個(gè)return語句時(shí),編譯器無法自動(dòng)推斷出返回類型,此時(shí)必須指定返回類型。

      //[捕獲列表](參數(shù)列表){函數(shù)體}
      int main()
      {
      auto Add = [](int a, int b) {
      return a + b;
      };
      std::cout << Add(1, 2) << std::endl; //輸出3
      return 0;
      }
      

      但是如果函數(shù)體內(nèi)有多個(gè)return語句時(shí),編譯器無法自動(dòng)推斷出返回類型,此時(shí)必須指定返回類型。

      4.2 捕獲列表

      有時(shí)候,需要在匿名函數(shù)內(nèi)使用外部變量,所以用捕獲列表來傳遞參數(shù)。根據(jù)傳遞參數(shù)的行為,捕獲列
      表可分為以下幾種:

      1 值捕獲

      與參數(shù)傳值類似,值捕獲的前提是變量可以拷貝,不同之處則在于,被捕獲的變量在 lambda表達(dá)式被
      創(chuàng)建時(shí)拷貝
      ,而非調(diào)用時(shí)才拷貝:

      void test3()
      {
      cout << "test3" << endl;
      int c = 12;
      int d = 30;
      auto Add = [c, d](int a, int b)->int {
      cout << "d = " << d << endl;
      return c;
      };
      d = 20;
      std::cout << Add(1, 2) << std::endl;
      }
      

      2 引用捕獲

      與引用傳參類似,引用捕獲保存的是引用,值會(huì)發(fā)生變化。
      如果Add中加入一句:c = a;

      void test5()
      {
      cout << "test5" << endl;
      int c = 12;
      int d = 30;
      auto Add = [&c, &d](int a, int b)->int {
      c = a; // 編譯對(duì)的
      cout << "d = " << d << endl;
      return c;
      };
      d = 20;
      std::cout << Add(1, 2) << std::endl;
      }
      
      

      3 隱式捕獲

      手動(dòng)書寫捕獲列表有時(shí)候是非常復(fù)雜的,這種機(jī)械性的工作可以交給編譯器來處理,這時(shí)候可以在捕獲
      列表中寫一個(gè) & 或 = 向編譯器聲明采用引用捕獲或者值捕獲。

      void test7()
      {
      cout << "test7" << endl;
      int c = 12;
      int d = 30;
      // 把捕獲列表的&改成=再測試
      auto Add = [&](int a, int b)->int {
      c = a; // 編譯對(duì)的
      cout << "d = " << d << endl;
      return c;
      };
      d = 20;
      std::cout << Add(1, 2) << std::endl;
      std::cout << "c:" << c<< std::endl;
      }
      

      4 空捕獲列表

      捕獲列表'[]'中為空,表示Lambda不能使用所在函數(shù)中的變量。

      void test8()
      {
      cout << "test7" << endl;
      int c = 12;
      int d = 30;
      // 把捕獲列表的&改成=再測試
      auto Add = [](int a, int b)->int {
      cout << "d = " << d << endl; // 編譯報(bào)錯(cuò)
      return c;// 編譯報(bào)錯(cuò)
      };
      d = 20;
      std::cout << Add(1, 2) << std::endl;
      std::cout << "c:" << c<< std::endl;
      }
      

      5 表達(dá)式捕獲

      上面提到的值捕獲、引用捕獲都是已經(jīng)在外層作用域聲明的變量,因此這些捕獲方式捕獲的均為左值,
      不能捕獲右值
      C++14之后支持捕獲右值,允許捕獲的成員用任意的表達(dá)式進(jìn)行初始化,被聲明的捕獲變量類型會(huì)根據(jù)
      表達(dá)式進(jìn)行判斷,判斷方式與使用 auto 本質(zhì)上是相同的:

      void test9()
      {
      cout << "test9" << endl;
      auto important = std::make_unique<int>(1);
      auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
      return x + y + v1 + (*v2);
      };
      std::cout << add(3,4) << std::endl;
      }
      

      6 泛型 Lambda

      在C++14之前,lambda表示的形參只能指定具體的類型,沒法泛型化。從 C++14 開始, Lambda 函數(shù)
      的形式參數(shù)可以使用 auto關(guān)鍵字來產(chǎn)生意義上的泛型:

      //泛型 Lambda C++14
      void test10()
      {
      cout << "test10" << endl;
      auto add = [](auto x, auto y) {
      return x+y;
      };
      std::cout << add(1, 2) << std::endl;
      std::cout << add(1.1, 1.2) << std::endl;
      }
      

      7 可變lambda

      • 采用值捕獲的方式,lambda不能修改其值,如果想要修改,使用mutable修飾
      • 采用引用捕獲的方式,lambda可以直接修改其值
      void test12() {
      cout << "test12" << endl;
      int v = 5;
      // 值捕獲方式,使用mutable修飾,可以改變捕獲的變量值
      auto ff = [v]() mutable {return ++v;};
      v = 0;
      auto j = ff(); // j為6
      }
      void test13() {
      cout << "test13" << endl;
      int v = 5;
      // 采用引用捕獲方式,可以直接修改變量值
      auto ff = [&v] {return ++v;};
      v = 0;
      auto j = ff(); // v引用已修改,j為1
      }
      

      總結(jié)

      1. 如果捕獲列表為[&],則表示所有的外部變量都按引用傳遞給lambda使用;
      2. 如果捕獲列表為[=],則表示所有的外部變量都按值傳遞給lambda使用;
      3. 匿名函數(shù)構(gòu)建的時(shí)候?qū)τ诎粗祩鬟f的捕獲列表,會(huì)立即將當(dāng)前可以取到的值拷貝一份作為常數(shù),然
        后將該常數(shù)作為參數(shù)傳遞。
        Lambda捕獲列表總結(jié)
      [] 空捕獲列表,Lambda不能使用所在函數(shù)中的變量。
      [names] names是一個(gè)逗號(hào)分隔的名字列表,這些名字都是Lambda所在函數(shù)的局部變量。默認(rèn)情況這些變量會(huì)被拷貝,然后按值傳遞,名字前面如果使用了&,則按引用傳遞
      [&] 隱式捕獲列表,Lambda體內(nèi)使用的局部變量都按引用方式傳遞
      [=] 隱式捕獲列表,Lanbda體內(nèi)使用的局部變量都按值傳遞
      [&,identifier_list] identifier_list是一個(gè)逗號(hào)分隔的列表,包含0個(gè)或多個(gè)來自所在函數(shù)的變量,這些變量采用值捕獲的方式,其他變量則被隱式捕獲,采用引用方式傳遞,identifier_list中的名字前面不能使用&。
      [=,identifier_list] identifier_list中的變量采用引用方式捕獲,而被隱式捕獲的變量都采用按值傳遞的方式捕獲。identifier_list中的名字不能包含this,且這些名字面前必須使用&。

      五 C++11標(biāo)準(zhǔn)庫(STL)

      STL定義了強(qiáng)大的、基于模板的、可復(fù)用的組件,實(shí)現(xiàn)了許多通用的數(shù)據(jù)結(jié)構(gòu)及處理這些數(shù)據(jù)結(jié)構(gòu)的算
      法。其中包含三個(gè)關(guān)鍵組件——容器(container,流行的模板數(shù)據(jù)結(jié)構(gòu))、迭代器(iterator)和算法
      (algorithm)。

      組件 描述
      容器 容器是用來管理某一類對(duì)象的集合。C++ 提供了各種不同類型的容器,比如 deque、list、vector、map 等。
      迭代器 迭代器用于遍歷對(duì)象集合的元素。這些集合可能是容器,也可能是容器的子集。
      算法 算法作用于容器。它們提供了執(zhí)行各種操作的方式,包括對(duì)容器內(nèi)容執(zhí)行初始化、排序、搜索和轉(zhuǎn)換等操作。

      5.1 容器簡介

      STL容器,可將其分為四類:序列容器、有序關(guān)聯(lián)容器、無序關(guān)聯(lián)容器、容器適配器、序列容器:

      標(biāo)準(zhǔn)庫容器類 描述
      array 固定大小,直接訪問任意元素
      deque 從前部或后部進(jìn)行快速插入和刪除操作,直接訪問任何元素
      forward_list 單鏈表,在任意位置快速插入和刪除
      list 雙向鏈表,在任意位置進(jìn)行快速插入和刪除操作
      vector 從后部進(jìn)行快速插入和刪除操作,直接訪問任意元素

      有序關(guān)聯(lián)容器(鍵按順序保存):

      標(biāo)準(zhǔn)庫容器類 描述
      set 快速查找,無重復(fù)元素
      multiset 快速查找,可有重復(fù)元素
      map 一對(duì)一映射,無重復(fù)元素,基于鍵快速查找
      multimap 一對(duì)一映射,可有重復(fù)元素,基于鍵快速查找

      無序關(guān)聯(lián)容器:

      標(biāo)準(zhǔn)庫容器類 描述
      unordered_set 快速查找,無重復(fù)元素
      unordered_multiset 快速查找,可有重復(fù)元素
      unordered_map 一對(duì)一映射,無重復(fù)元素,基于鍵快速查找
      unordered_multimap 一對(duì)一映射,可有重復(fù)元素,基于鍵快速查找

      容器適配器:

      標(biāo)準(zhǔn)庫容器類 描述
      stack 后進(jìn)先出(LIFO)
      queue 先進(jìn)先出(FIFO)
      priority_queue 優(yōu)先級(jí)最高的元素先出

      序列容器描述了線性的數(shù)據(jù)結(jié)構(gòu)(也就是說,其中的元素在概念上” 排成一行"), 例如數(shù)組、向量和 鏈
      表。
      關(guān)聯(lián)容器描述非線性的容器,它們通常可以快速鎖定其中的元素。這種容器可以存儲(chǔ)值的集合或者鍵-
      值對(duì)

      棧和隊(duì)列都是在序列容器的基礎(chǔ)上加以約束條件得到的,因此STL把stack和queue作為容器適配器來實(shí)
      現(xiàn),這樣就可以使程序以一種約束方式來處理線性容器。類型string支持的功能跟線性容器一樣, 但是
      它只能存儲(chǔ)字符數(shù)據(jù).

      5.2 迭代器簡介

      迭代器在很多方面與指針類似,也是用于指向首類容器中的元素(還有一些其他用途,后面將會(huì)提
      到)。 迭代器存有它們所指的特定容器的狀態(tài)信息,即迭代器對(duì)每種類型的容器都有一個(gè)實(shí)現(xiàn)。 有些迭
      代器的操作在不同容器間是統(tǒng)一的。 例如,*運(yùn)算符間接引用一個(gè)迭代器,這樣就可以使用它所指向的
      元素。++運(yùn)算符使得迭代器指向容器中的下一個(gè)元素(和數(shù)組中指針遞增后指向數(shù)組的下一個(gè)元素類
      似)。
      STL 首類容器提供了成員函數(shù) begin 和 end。函數(shù) begin 返回一個(gè)指向容器中第一個(gè)元素的迭代器,函
      數(shù) end 返回一個(gè)指向容器中最后一個(gè)元素的下一個(gè)元素(這個(gè)元素并不存在,常用于判斷是否到達(dá)了容
      器的結(jié)束位僅)的迭代器。 如果迭代器 i 指向一個(gè)特定的元素,那么 ++i 指向這個(gè)元素的下一個(gè)元素。*
      i 指代的是i指向的元素。 從函數(shù) end 中返回的迭代器只在相等或不等的比較中使用,來判斷這個(gè)“移動(dòng)
      的迭代器” (在這里指i)是否到達(dá)了容器的末端。
      使用一個(gè) iterator 對(duì)象來指向一個(gè)可以修改的容器元素,使用一個(gè) const_iterator 對(duì)象來指向一個(gè)不能
      修改的容器元素。

      類型 描述
      隨機(jī)訪問迭代器(randomaccess) 在雙向迭代湍基礎(chǔ)上增加了直接訪問容器中任意元素的功能, 即可以向前或
      向后跳轉(zhuǎn)任意個(gè)元素
      雙向迭代器(bidirectional) 在前向迭代器基礎(chǔ)上增加了向后移動(dòng)的功能。支持多遍掃描算法
      前向迭代器(forword) 綜合輸入和輸出迭代器的功能,并能保持它們?cè)谌萜髦械奈恢茫ㄗ鳛闋顟B(tài)信息),可以使用同一個(gè)迭代器兩次遍歷一個(gè)容器(稱為多遍掃描算法)
      輸出迭代器(output) 用于將元素寫入容器。 輸出迭代楛每次只能向前移動(dòng)一個(gè)元索。 輸出迭代器只支持一遍掃描算法,不能使用相同的輸出迭代器兩次遍歷一個(gè)序列容器
      輸入迭代器(input) 用于從容器讀取元素。 輸入迭代器每次只能向前移動(dòng)一個(gè)元素。 輸入迭代器只支持一遍掃描算法,不能使用相同的輸入迭代器兩次遍歷一個(gè)序列容器

      每種容器所支持的迭代器類型決定了這種容器是否可以在指定的 STL 算 法中使用。 支持隨機(jī)訪問迭代
      器的容器可用于所有的 STL 算法(除了那些需要改變?nèi)萜鞔笮〉乃惴ǎ@樣的算法不能在數(shù)組和 array
      對(duì)象中使用)。 指向 數(shù)組的指針可以代替迭代器用于幾乎所有的 STL 算法中,包括那些要求隨機(jī)訪問
      迭代器的算法。 下表顯示了每種 STL 容器所支持的迭代器類型。 注意, vector 、 deque 、 list 、 set、 multiset 、 map 、 multimap以及 string 和數(shù)組都可以使用迭代器遍歷.

      容器 支持的迭代器類型 容器 支持的迭代器類型
      vector 隨機(jī)訪問迭代器 set 雙向迭代器
      array 隨機(jī)訪問迭代器 multiset 雙向迭代器
      deque 隨機(jī)訪問迭代器 map 雙向迭代器
      list 雙向迭代器 multimap 雙向迭代器
      forword_list 前向迭代器 unordered_set 雙向迭代器
      stack 不支持迭代器 unordered_multiset 雙向迭代器
      queue 不支持迭代器 unordered_map 雙向迭代器
      priority_queue 不支持迭代器 unordered_multimap 雙向迭代器

      下表顯示了在 STL容器的類定義中出現(xiàn)的幾種預(yù)定義的迭代器 typedef。不是每種 typedef 都出現(xiàn)在每
      個(gè)容器中。 我們使用常量版本的迭代器來訪問只讀容器或不應(yīng)該被更改的非只讀容器,使用反向迭代器
      來以相反的方向訪問容器。

      為迭代器預(yù)先定義的typedef ++的方向 讀寫能力
      iterator 向前 讀/寫
      const_iterator 向前
      reverse_iterator 向后 讀/寫
      const_reverse_iterator 向后

      下表顯示了可作用在每種迭代器上的操作。 除了給出的對(duì)于所有迭代器都有的運(yùn)算符,迭代器還必須提
      供默認(rèn)構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)和拷貝賦值操作符。 前向迭代器支持++ 和所有的輸入和輸出迭代器的
      功能。 雙向迭代器支持–操作和前向迭代器的功能。 隨機(jī)訪問迭代器支持所有在表中給出的操作。 另
      外, 對(duì)于輸入迭代器和輸出迭代器,不能在保存迭代器之后再使用保存的值

      迭代器操作 描述
      適用所有迭代器的操作
      ++p 前置自增迭代器
      p++ 后置自增迭代器
      p=p1 將一個(gè)迭代器賦值給另一個(gè)迭代器
      輸入迭代器
      *p 間接引用一個(gè)迭代器
      p->m 使用迭代器讀取元素m
      p==p1 比較兩個(gè)迭代器是否相等
      p!=p1 比較兩個(gè)迭代器是否不相等
      輸出迭代器
      *p 間接引用一個(gè)迭代器
      p=p1 把一個(gè)迭代器賦值給另一個(gè)
      前向迭代器 前向迭代器提供了輸入和輸出迭代器的所有功能
      雙向迭代器
      –p q
      p– 后置自減迭代器
      隨機(jī)訪問迭代器
      p+=i 迭代器p前進(jìn)i個(gè)位置
      p-=i 迭代器p后退i個(gè)位置
      p+i 在迭代器p 的位置上前進(jìn)i個(gè)位置
      p-i 在迭代器p的位置上后退i個(gè)位置
      p-p1 表達(dá)式的值是一個(gè)整數(shù),它代表同一個(gè)容器中兩個(gè)元素間的距離
      p[i] 返回與迭代器p的位置相距i的元素
      p<p1 若迭代器p小于p1(即容器中p在p1前)則返回 true, 否則返回 false
      p<=p1 若迭代器p小千或等于p1 (即容器中p 在p1前或位咒相同)則返回 true, 否則返回 false
      p>p1 若迭代器p 大于p1(即容器中p在p1后)則返回true, 否則返回false
      p>=p1 若迭代器p大于或等于p1(即容楛中p在p1后或位置相同)則返回 true, 否則返回 false

      5.3 map與unordered_map(紅黑樹VS哈希表)

      C++11 增加了無序容器 unordered_map/unordered_multimap 和
      unordered_set/unordered_multiset,由于這些容器中的元素是不排序的,因此,比有序容器
      map/multimap 和 set/multiset 效率更高。 map 和 set 內(nèi)部是紅黑樹,在插入元素時(shí)會(huì)自動(dòng)排序,而
      無序容器內(nèi)部是散列表( Hash Table),通過哈希( Hash),而不是排序來快速操作元素,使得效率
      更高。由于無序容器內(nèi)部是散列表,因此無序容器的 key 需要提供 hash_value 函數(shù),其他用法和
      map/set 的用法是一樣的。不過對(duì)于自定義的 key,需要提供 Hash 函數(shù)和比較函數(shù)。

      5.3.1 map和unordered_map的差別

      需要引入的頭文件不同

      map: #include < map >
      unordered_map: #include < unordered_map >

      內(nèi)部實(shí)現(xiàn)機(jī)理不同
      • map: map內(nèi)部實(shí)現(xiàn)了一個(gè)紅黑樹(紅黑樹是非嚴(yán)格平衡二叉搜索樹,而AVL是嚴(yán)格平衡二叉搜
        索樹),紅黑樹具有自動(dòng)排序的功能,因此map內(nèi)部的所有元素都是有序的,紅黑樹的每一個(gè)節(jié)點(diǎn)
        都代表著map的一個(gè)元素。
      • unordered_map: unordered_map內(nèi)部實(shí)現(xiàn)了一個(gè)哈希表(也叫散列表,通過把關(guān)鍵碼值映射到
        Hash表中一個(gè)位置來訪問記錄,查找的時(shí)間復(fù)雜度可達(dá)到O(1),其在海量數(shù)據(jù)處理中有著廣泛應(yīng)
        用)。因此,其元素的排列順序是無序的。

      5.3.2 優(yōu)缺點(diǎn)以及適用處

      map:

      1. 優(yōu)點(diǎn):
      • 有序性,這是map結(jié)構(gòu)最大的優(yōu)點(diǎn),其元素的有序性在很多應(yīng)用中都會(huì)簡化很多的操作
      • 紅黑樹,內(nèi)部實(shí)現(xiàn)一個(gè)紅黑書使得map的很多操作在lgn的時(shí)間復(fù)雜度下就可以實(shí)現(xiàn),因此效率非
        常的高
      1. 缺點(diǎn):
      • 空間占用率高,因?yàn)閙ap內(nèi)部實(shí)現(xiàn)了紅黑樹,雖然提高了運(yùn)行效率,但是因?yàn)槊恳粋€(gè)節(jié)點(diǎn)都需要額
        外保存父節(jié)點(diǎn)、孩子節(jié)點(diǎn)和紅/黑性質(zhì),使得每一個(gè)節(jié)點(diǎn)都占用大量的空間
      1. 適用處:
        對(duì)于那些有順序要求的問題,用map會(huì)更高效一些

      unordered_map:

      1. 優(yōu)點(diǎn): 因?yàn)閮?nèi)部實(shí)現(xiàn)了哈希表,因此其查找速度非常的快
      2. 缺點(diǎn): 哈希表的建立比較耗費(fèi)時(shí)間
      3. 適用處:對(duì)于查找問題,unordered_map會(huì)更加高效一些,因此遇到查找問題,常會(huì)考慮一下用unordered_map

      5.3.3 總結(jié)

      1. 內(nèi)存占有率的問題就轉(zhuǎn)化成紅黑樹 VS hash表 , 還是unorder_map占用的內(nèi)存要高。
      2. 但是unordered_map執(zhí)行效率要比map高很多
      3. 對(duì)于unordered_map或unordered_set容器,其遍歷順序與創(chuàng)建該容器時(shí)輸入的順序不一定相
        同,因?yàn)楸闅v是按照哈希表從前往后依次遍歷的

      推薦一個(gè)零聲學(xué)院免費(fèi)教程,個(gè)人覺得老師講得不錯(cuò),
      分享給大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
      fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,
      TCP/IP,協(xié)程,DPDK等技術(shù)內(nèi)容,點(diǎn)擊立即學(xué)習(xí):
      服務(wù)器
      音視頻
      dpdk
      Linux內(nèi)核

      posted @ 2022-08-23 20:21  飄雨的河  閱讀(241)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 永久免费观看美女裸体的网站| 国产精品538一区二区在线| 无码av最新无码av专区| 天天澡日日澡狠狠欧美老妇| 国产精品久久久久久久久电影网| 伊伊人成亚洲综合人网香| 国产成人8X人网站视频| 精品人妻人人做人人爽| 日本国产精品第一页久久| 国产亚洲精品自在久久vr| 亚洲宅男精品一区在线观看| 国产v综合v亚洲欧美大天堂| 亚洲日韩中文字幕在线播放| 伊人久久综合无码成人网| 国产盗摄xxxx视频xxxx| 国产成人亚洲日韩欧美| 乱色欧美激惰| 国产真人做受视频在线观看| 亚洲免费最大黄页网站| 日韩 一区二区在线观看| 人妻少妇久久中文字幕| 欧美日韩国产亚洲沙发| 洪泽县| 精品国产乱码久久久久app下载| 国产真实精品久久二三区| 丁香婷婷综合激情五月色| 精品尤物国产尤物在线看 | 小污女小欲女导航| 国产 精品 自在 线免费| 中文字幕结果国产精品| 国产精品午夜av福利| 久久国产精品日本波多野结衣| 九九热精彩视频在线免费| 午夜福利在线观看入口| 白丝乳交内射一二三区| 在线看av一区二区三区| 欧美大胆老熟妇乱子伦视频| 亚洲精品香蕉一区二区| 麻豆精品一区二区三区蜜桃| 深夜福利资源在线观看| 国产成人精品视频不卡|