Chromium智能指針使用指南

什么是智能指針?

智能指針是一種特殊類型的“局部對(duì)象”,表現(xiàn)如同裸指針,但是具備離開(kāi)作用域(out of scope)時(shí)主動(dòng)釋放所指向?qū)ο?/code>的能力。因?yàn)镃++沒(méi)有垃圾回收機(jī)制,因此智能指針的特性顯得非常重要。

下面是最常用智能指針類型std::unique_ptr<>的例子:

  // 我們可以在構(gòu)造std::unique_prt<>的時(shí)候傳入指針
  std::unique_ptr value(base::JSONReader::Read(data));
  std::unique_ptr foo_ptr(new Foo(...));
  
  // ...或者使用reset()
  std::unique_ptr bar_ptr;      // 與 "Bar* bar_ptr = nullptr;" 相似.
  bar_ptr.reset(new Bar(...));  // 此時(shí) |bar_ptr| 不為空且持有對(duì)象 
  
  // 我們可以用 () 檢查std::unique_ptr<>是否為空
  if (!value)
    return false;
  
  // get() 訪問(wèn)持有的裸指針
  Foo* raw_ptr = foo_ptr.get();
  
  // 我們可以像使用裸指針一樣調(diào)用std::unique_ptr<>的方法
  DictionaryValue* dict;
  if (!value->GetAsDictionary(&dict))
    return false;

為什么我們要使用智能指針?

即使對(duì)象的創(chuàng)建和析構(gòu)時(shí)機(jī)不確定,使用智能指針確保我們能正確釋放對(duì)象。無(wú)論方法里有再多且邏輯復(fù)雜的路徑,智能指針總能確保局部變量正確的釋放,且能明確對(duì)象的所有權(quán),避免程序內(nèi)存泄漏或者對(duì)象重復(fù)釋放。最后,在方法調(diào)用時(shí),需要明確指出對(duì)象擁有權(quán)的轉(zhuǎn)移和結(jié)果。

存在哪些類型的智能指針?

在Chromium里最常用的兩種智能指針類型是std::unique_ptr<>scoped_refptr<>。前者適用于單一所有權(quán)的對(duì)象,后者適用于引用計(jì)數(shù)的對(duì)象(然而,通常應(yīng)該避免使用引用計(jì)數(shù)的對(duì)象)。如果你比較熟悉C++11,會(huì)發(fā)現(xiàn)scoepd_refptr<>std::shared_ptr<>用法很相似。

base/memory/ 還定義了其余幾種類型的對(duì)象:

  • linked_ptr<> 用于在C++11之前存放智能指針對(duì)象,已被廢棄?,F(xiàn)在Chromium已經(jīng)支持C++11了,我們不應(yīng)該再使用linked_ptr<>了,而應(yīng)該在STL容器里使用std::unique_ptr<>。
  • ScopedVector<>也被廢棄了。它是一種vector,并且持有容器內(nèi)對(duì)象的所有權(quán)。請(qǐng)使用std::vector<std::unique_ptr<>>代替。
  • WeakPtr<>實(shí)際上不是智能指針。它的表現(xiàn)像指針類型,但是并不能用來(lái)自動(dòng)釋放對(duì)象,通常用作追蹤其它地方擁有的對(duì)象是否依然存活,當(dāng)追蹤對(duì)象釋放時(shí),WeakPtr<>會(huì)自動(dòng)的置為null。(但是依然需要在解引用前判斷是否為null,因?yàn)榻庖?code>null WeakPtr<>等于于解引用null,而不是no-op。)WeakPtr<>與C++11的std::weak_ptr<>作用比較相似,但是使用了不同的API并且少了許多使用限制。

如何選擇使用哪種智能指針?

  • 單一所有權(quán)的對(duì)象。使用std::unique_ptr。需要注意的是,std::unique_ptr持有的需要必須是非引用計(jì)數(shù)的,并且分配在堆上的對(duì)象。
  • 無(wú)所有權(quán)的對(duì)象。使用裸指針或者WeakPtr<>。注意WeakPtr<>只能在創(chuàng)建它的線程解引用(通常使用WeakPtrFactory<>)。如果你需要在對(duì)象釋放前后立刻執(zhí)行某些操作,那么可能使用callback或notification更適合,而不是WeakPtr<>
  • 引用計(jì)數(shù)的對(duì)象。使用scoped_refptr<>,但是最好是重新考慮使用引用計(jì)數(shù)對(duì)象是否合理。引用計(jì)數(shù)對(duì)象很難明確擁有權(quán)和析構(gòu)順序,特別是在多線程環(huán)境中??偸怯蟹椒▉?lái)重新設(shè)計(jì)引對(duì)象層級(jí)來(lái)避免引用計(jì)數(shù)的。限制每個(gè)類都只能在單個(gè)線程工作,并且使用PostTask()確保調(diào)用在正確的線程,這樣有助于在多線程中避免引用計(jì)數(shù)。base::Bind()WeakPtr<>等工具具備在對(duì)象釋放時(shí)自動(dòng)取消方法調(diào)用的能力。Chromium中依然有許多代碼在使用引用計(jì)數(shù)對(duì)象,如果你看見(jiàn)Chromium中有代碼這樣做但并不代表這是合理的解決方案。
  • 平臺(tái)特定類型。使用平臺(tái)特定的對(duì)象,譬如base::win::ScopedHandle,base::win::ScopedComPtr,或者base::mac::ScopedCFTypeRef。需要注意的是這些類型使用方式可能和std::unique_ptr<>不同。

不同類型指針間調(diào)用規(guī)定是怎樣的?

calling conventions section of the Chromium style guide有規(guī)定。下面列出一些常用的規(guī)定。

  • 如果方法參數(shù)里使用std::unique_ptr<> ,說(shuō)明該方法需占用傳入?yún)?shù)的所有權(quán),調(diào)用方需要使用std::move()來(lái)表明轉(zhuǎn)移對(duì)象的所有權(quán)。需要注意的是,臨時(shí)對(duì)象不需要調(diào)用std::move()轉(zhuǎn)移所有權(quán)。

      // Foo() 擁有 |bar| 的所有權(quán).
      void Foo(std::unique_ptr<Bar> bar);
      
      ...
      std::unique_ptr<Bar> bar_ptr(new Bar());
      Foo(std::move(bar_ptr));          // 調(diào)用后,|bar_ptr| 被置為 null.
      Foo(std::unique_ptr<Bar>(new Bar()));  // 臨時(shí)對(duì)象不需要調(diào)用std::move()
    
  • 如果方法的返回值使用std::unique_ptr<>,說(shuō)明調(diào)用方需要持有返回對(duì)象的所有權(quán)。這種情況下,當(dāng)且僅當(dāng)返回對(duì)象類型和臨時(shí)對(duì)象的類型不同時(shí),需要使用std::move()。

      class Base { ... };
      class Derived : public Base { ... };
      
      // Foo 擁有|base|的所有權(quán), 調(diào)用方擁有 返回值對(duì)象 的所有權(quán)
      std::unique_ptr<Base> Foo(std::unique_ptr<Base> base) {
        if (cond) {
          // 轉(zhuǎn)移 |base| 的所有權(quán)給調(diào)用方
          return base;                           
        }
        
        // 注意這種場(chǎng)景下,方法運(yùn)行結(jié)束時(shí),|base|會(huì)被釋放掉
        if (cond2) {
          // 臨時(shí)對(duì)象不需要調(diào)用std::move()
          return std::unique_ptr<Base>(new Base()));  
        }
        std::unique_ptr<Derived> derived(new Derived());
        // 注意需要使用std::move(),因?yàn)閨derived|的類型和返回值的類型不同。
        return std::move(derived);
      }
    
  • 如果方法傳入或者返回裸指針,表示無(wú)需所有權(quán)轉(zhuǎn)移。Chromium在std::unique_ptr<>存在之前寫的一些代碼,或者不熟悉所有權(quán)轉(zhuǎn)移的程序員寫的代碼,可能會(huì)在傳入或者返回裸指針的時(shí)候也使用std::move()轉(zhuǎn)移了所有權(quán)。但是這樣做是不安全的,編譯器并不能執(zhí)行正確的表現(xiàn)。去掉這樣的代碼吧,方法傳入或者返回裸指針時(shí),絕對(duì)不要轉(zhuǎn)移所有權(quán)。

可以通過(guò)引用傳遞參數(shù)或者返回值嗎?

不要這樣做。

原理上來(lái)說(shuō),傳入const std::unique_ptr<T> &參數(shù)并且不轉(zhuǎn)移所有權(quán)比傳入T*有優(yōu)勢(shì),這樣做可以防止調(diào)用方傳入錯(cuò)誤的參數(shù)(譬如把 int 轉(zhuǎn)成了 T*),而且調(diào)用方必須確保方法調(diào)用周期內(nèi)傳入對(duì)象不會(huì)被釋放。但是,這樣調(diào)用方就必須把傳入對(duì)象生成在堆上,即使調(diào)用方原本可以使對(duì)象生成在棧上。這里傳入裸指針相比傳入const std::unique_ptr<T> &的好處是,可以將對(duì)象所有權(quán)的問(wèn)題和對(duì)象生成的問(wèn)題解耦。為了簡(jiǎn)潔和統(tǒng)一,我們避免開(kāi)發(fā)人員去權(quán)衡這些利弊,總是使用裸指針就好了。

有個(gè)例外,在lambda表達(dá)式中,若將智能指針?lè)旁赟TL容器里作為參數(shù)傳遞,這里為了編譯通過(guò),必須使用const std::unique_ptr<T> &。

我想使用STL容器用來(lái)持有指針對(duì)象。此時(shí)可以用智能指針嗎?

可以。在C++11里,你可以將智能指針?lè)湃隨TL容器內(nèi)。而且,不要再使用ScopedVector<T>了,使用std::vector<std::unique_ptr<T>>來(lái)替代。同樣的,再也不要再使用linked_ptr<T>了,直接把智能智能放在STL容器里使用即可。

引用資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 什么是智能指針? 智能指針是行為類似于指針的類對(duì)象,但這種對(duì)象還有其他功能。 2. 為什么設(shè)計(jì)智能指針? 引...
    MinoyJet閱讀 708評(píng)論 0 1
  • 導(dǎo)讀## 最近在補(bǔ)看《C++ Primer Plus》第六版,這的確是本好書(shū),其中關(guān)于智能指針的章節(jié)解析的非常清晰...
    小敏紙閱讀 2,086評(píng)論 1 12
  • C++智能指針 原文鏈接:http://blog.csdn.net/xiaohu2022/article/deta...
    小白將閱讀 6,995評(píng)論 2 21
  • 總結(jié) unique_ptr指針的一些特性總結(jié) 默認(rèn)情況下,占用的內(nèi)存大小和raw指針一樣。(除非指定了用戶自定義d...
    EVANMORE閱讀 1,197評(píng)論 0 2
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,680評(píng)論 1 51

友情鏈接更多精彩內(nèi)容