什么是智能指針?
智能指針是一種特殊類型的“局部對(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容器里使用即可。