智慧型指標 (現代 C++)

在現代 C++ 程式設計中,標準程式庫包含「智慧型指標」(Smart Pointer),使用它可確保程式免於流失記憶體及資源,且無例外狀況之虞。

智慧型指標的用途

智慧型指標是在 <memory> 標頭檔的 std 命名空間定義的。 它們對「RAII」或「資源擷取為初始設定」(Resource Acquisition Is Initialialization) 程式設計慣用語而言非常重要。 這個慣用語主要目標是確保在初始化物件的同時會進行擷取資源,使建立物件的所有資源在一行程式碼中建立並且準備就緒。 實際上,RAII 的主要原則就是將任何堆積配置資源的擁有權 (例如,動態配置記憶體或系統物件控制代碼) 提供給含有刪除或釋放資源程式碼的解構函式,以及任何相關清除程式碼的堆疊配置物件。

大部分情況下,當您初始化原始指標或資源控制代碼以指向實際資源時,會立即將指標傳遞給智慧型指標。 在現代 C++ 中,原始指標只能用於極重視效能且擁有權不會混淆之有限範圍的小型程式碼區塊、迴圈或 Helper 函式。

下列範例將原始指標宣告與智慧型指標宣告做比較。

void UseRawPointer()
{
    // Using a raw pointer -- not recommended.
    Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); 

    // Use pSong... 

    // Don't forget to delete! 
    delete pSong;   
}


void UseSmartPointer()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));

    // Use song2...
    wstring s = song2->duration_;
    //...

} // song2 is deleted automatically here.

如範例所示,智慧型指標是在堆疊上宣告、並且以指向堆積配置物件的原始指標初始化的類別樣板。 智慧型指標在初始化後,就會擁有原始指標。 這表示智慧型指標會刪除原始指標指定的記憶體。 智慧型指標解構函式包含對 delete 的呼叫,而且因為智慧型指標是在堆疊上宣告,因此當智慧型指標超出範圍時,便會叫用其解構函式,即使例外狀況是從比堆疊更上方的位置擲回亦然。

使用熟悉的指標運算子 -> 和 * (智慧型指標類別會進行多載以傳回封裝的原始指標) 存取封裝的指標。

C++ 智慧型指標慣用語與在 C# 等語言中建立物件的情形類似:您會建立物件,然後讓系統負責在適當時機將其刪除。 差別在於背景中沒有執行是沒有個別記憶體回收行程;記憶體是透過標準 C++ 範圍設定規則來管理,因此執行階段環境會更快速且更有效率。

重要

請一律在不同的程式碼行上建立智慧型指標,絕不可在參數清單建立,如此可避免因某些參數清單配置規則,而發生無法預測的資源流失。

下列範例示範如何使用標準樣板程式庫中的 unique_ptr 智慧型指標類型,封裝大型物件的指標。

class LargeObject
{
public:
    void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

} //pLarge is deleted automatically when function block goes out of scope.

下列範例示範使用智慧型指標的基本步驟。

  1. 將智慧型指標宣告為自動 (區域) 變數。(請勿在智慧型指標本身使用 new 或 malloc 運算式)。

  2. 在類型參數中,指定封裝指標的指向類型。

  3. 將原始指標傳遞至智慧型指標建構函式中使用 new 配置的物件。(某些公用程式函式或智慧型指標建構函式即可進行此步驟)。

  4. 使用多載的 -> 和 * 運算子來存取物件。

  5. 讓智慧型指標刪除物件。

智慧型指標是針對在記憶體和效能兩方面都達到最大效率而設計。 例如,封裝的指標 unique_ptr 的唯一資料成員。 這表示 unique_ptr 與該指標有完全相同的大小,四個位元組或八個位元組。 使用智慧型指標多載 * and -> 運算子存取封裝的指標,相較於直接存取原始指標,在速度上並沒有顯著降低。

智慧型指標有自己的成員函式,是使用「點」標記法來存取。 例如,某些 STL 智慧型指標有重設成員函式,會釋放指標的擁有權。 當您要在智慧型指標超出範圍之前釋出智慧型指標所擁有的記憶體時,這將會很有用,如下列範例所示。

void SmartPointerDemo2()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Free the memory before we exit function block.
    pLarge.reset();

    // Do some other work...

}

智慧型指標通常會提供直接存取其原始指標的方法。 STL 智慧型指標具有用於此用途的 get 成員函式,而 CComPtr 則有公用 p 類別成員。 智慧型指標可讓您直接存取基礎指標,您可以用來在自己的程式碼管理記憶體,而且仍然可以將原始指標傳遞至不支援智慧型指標的程式碼。

void SmartPointerDemo4()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass raw pointer to a legacy API
    LegacyLargeObjectFunction(pLarge.get());    
}

智慧型指標種類

下列章節中將摘要說明可在 Windows 程式設計環境中使用的各種不同智慧型指標,並描述其使用時機。

  • C++ 標準程式庫智慧型指標
    使用這些智慧型指標做為封裝純舊 C++ 物件 (POCO) 指標的首要選擇。

    • unique_ptr
      只允許一個基礎指標的擁有者。 用做 POCO 的預設選項,除非您確信自己需要 shared_ptr。 可以移至新擁有者,但不可複製或共用。 替換已被取代的 auto_ptr。 與 boost::scoped_ptr 比較。 unique_ptr 既小又有效率,大小是一個指標,而且支援用於對 STL 集合進行快速插入和擷取的右值參考。 標頭檔:<memory>。 如需詳細資訊,請參閱 如何:建立和使用 unique_ptr 執行個體unique_ptr 類別

    • shared_ptr
      參考計數的智慧型指標。 在您想要將原始指標指派給多個擁有者時使用,例如,當您從容器傳回指標的複本,但是想要保留原來的指標時。 原始指標只有在所有的 shared_ptr 擁有者都超出範圍或放棄擁有權之後,才會被刪除。 大小是兩個指標;一個針對物件,另一個則針對含有參考計數的共用控制區塊。 標頭檔:<memory>。 如需詳細資訊,請參閱 如何:建立和使用 shared_ptr 執行個體shared_ptr 類別

    • weak_ptr
      shared_ptr 一起使用的特殊案例智慧型指標。 weak_ptr 可以讓您存取由一個或多個 shared_ptr 執行個體擁有的物件,但是不會參與參考計數。 當您想要觀察物件,但不需要物件保持運作時,即可使用。 在某些要中斷 shared_ptr 執行個體之間循環參考的情況下為必要項。 標頭檔:<memory>。 如需詳細資訊,請參閱 如何:建立和使用 weak_ptr 執行個體weak_ptr 類別

  • COM 物件 (傳統 Windows 程式設計) 的智慧型指標
    當您使用 COM 物件時,請將介面指標包裝在適當的智慧型指標類型中。 Active Template Library (ATL) 會定義數個各種用途的智慧型指標。 您也可以使用 _com_ptr_t 智慧型指標類型,編譯器會在從 .tlb 檔案建立包裝函式類別時使用這個類型。 當您不想要包含 ATL 標頭檔時,這是最佳選擇。

  • POCO 物件的 ATL 智慧型指標
    除了 COM 物件的智慧型指標之外,ATL 還定義了純舊 C++ 物件的智慧標籤指標 (及智慧型指標集合)。 在傳統 Windows 程式設計中,這些類型是 STL 集合的實用替代方式,特別是在不需要程式碼可攜性,或者不想要混用 STL 和 ATL 的程式設計模型時。

請參閱

其他資源

歡迎回到 C++ (現代 C++)

C++ 語言參考

C++ 標準程式庫參考

在 C++ 程式概觀: 記憶體管理