C++怎么使用智能指針 C++智能指針的類型與使用場景

c++++智能指針通過raii機制自動管理內存,避免內存泄漏。1. unique_ptr實現獨占式所有權,確保同一時間只有一個指針指向對象,支持顯式轉移所有權,適用于資源管理和工廠函數返回值;2. shared_ptr采用引用計數實現共享所有權,最后一個shared_ptr銷毀時釋放對象,適合多指針共享資源的場景,并可結合weak_ptr解決循環引用;3. weak_ptr提供弱引用,不增加引用計數,用于觀察對象狀態或打破shared_ptr間的循環引用;4. 推薦使用make_shared創建shared_ptr,提升異常安全性和性能;5. 線程環境下shared_ptr線程安全但對象訪問需同步,unique_ptr非線程安全;6. 自定義刪除器可用于釋放非內存資源或執行特定清理操作;7. 選擇智能指針時需權衡所有權模型、性能及線程安全等因素,慎用與原始指針的交互以避免重復釋放或懸掛指針問題。

C++怎么使用智能指針 C++智能指針的類型與使用場景

c++智能指針本質上是為了更好地管理動態分配的內存,避免內存泄漏。它們通過RaiI(Resource Acquisition Is Initialization)機制,在對象生命周期結束時自動釋放資源。

C++怎么使用智能指針 C++智能指針的類型與使用場景

unique_ptr、shared_ptr、weak_ptr

C++怎么使用智能指針 C++智能指針的類型與使用場景

unique_ptr

立即學習C++免費學習筆記(深入)”;

C++怎么使用智能指針 C++智能指針的類型與使用場景

unique_ptr是獨占式指針,意味著同一時間只能有一個unique_ptr指向特定的對象。它非常輕量級,開銷幾乎與原始指針相同,而且保證了對象所有權的唯一性。

#include <iostream> #include <memory>  class MyClass { public:     MyClass() { std::cout << "MyClass constructedn"; }     ~MyClass() { std::cout << "MyClass destructedn"; }     void doSomething() { std::cout << "Doing something...n"; } };  int main() {     std::unique_ptr<MyClass> ptr(new MyClass()); // 創建 unique_ptr     ptr->doSomething(); // 使用 -> 訪問對象成員      // std::unique_ptr<MyClass> ptr2 = ptr; // 錯誤!不能復制 unique_ptr      std::unique_ptr<MyClass> ptr2 = std::move(ptr); // 正確,轉移所有權     if (ptr) {         ptr->doSomething(); // 不會執行,因為 ptr 已經為空     }     if (ptr2) {         ptr2->doSomething(); // ptr2 現在擁有對象     }      return 0; // ptr2 析構,MyClass 對象被銷毀 }

unique_ptr的主要用途:

  • 資源管理: 確保動態分配的對象在不再需要時被自動釋放。
  • 所有權轉移: 通過std::move顯式地轉移所有權。
  • 工廠函數: 用于從工廠函數返回動態分配的對象。

shared_ptr

shared_ptr允許多個指針指向同一個對象,它使用引用計數來跟蹤有多少個shared_ptr指向該對象。當最后一個shared_ptr被銷毀時,對象才會被釋放。

#include <iostream> #include <memory>  class MyClass { public:     MyClass() { std::cout << "MyClass constructedn"; }     ~MyClass() { std::cout << "MyClass destructedn"; }     void doSomething() { std::cout << "Doing something...n"; } };  int main() {     std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(); // 推薦使用 make_shared     std::shared_ptr<MyClass> ptr2 = ptr1; // 共享所有權      std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl; // 輸出 2     std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl; // 輸出 2      ptr1->doSomething();     ptr2->doSomething();      ptr1.reset(); // 釋放 ptr1 的所有權      std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl; // 輸出 0     std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl; // 輸出 1      return 0; // ptr2 析構,MyClass 對象被銷毀 }

shared_ptr的主要用途:

  • 共享所有權: 當多個對象需要共享對一個資源的所有權時。
  • 循環引用處理: 與weak_ptr結合使用,打破循環引用。
  • 并發編程: 在多線程環境中安全地共享資源。

weak_ptr

weak_ptr是一種弱引用,它指向由shared_ptr管理的對象,但不增加引用計數。weak_ptr不能直接訪問對象,必須先轉換為shared_ptr才能使用。當shared_ptr管理的對象被銷毀時,weak_ptr會自動失效。

#include <iostream> #include <memory>  class MyClass { public:     MyClass() { std::cout << "MyClass constructedn"; }     ~MyClass() { std::cout << "MyClass destructedn"; }     void doSomething() { std::cout << "Doing something...n"; } };  int main() {     std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();     std::weak_ptr<MyClass> weakPtr = sharedPtr;      if (auto ptr = weakPtr.lock()) { // 嘗試獲取 shared_ptr         ptr->doSomething(); // 安全地訪問對象         std::cout << "Object is still aliven";     } else {         std::cout << "Object has been destroyedn";     }      sharedPtr.reset(); // 釋放 shared_ptr 的所有權      if (auto ptr = weakPtr.lock()) {         ptr->doSomething(); // 不會執行         std::cout << "Object is still aliven";     } else {         std::cout << "Object has been destroyedn"; // 輸出此行     }      return 0; }

weak_ptr的主要用途:

  • 觀察者模式: 允許觀察對象的狀態,而不會影響對象的生命周期。
  • 緩存: 緩存對象,當對象不再被使用時自動釋放。
  • 打破循環引用: 解決shared_ptr可能導致的循環引用問題。

如何選擇合適的智能指針?

在選擇智能指針時,需要考慮以下因素:

  • 所有權模型: 是否需要獨占所有權還是共享所有權?
  • 性能: unique_ptr開銷最小,shared_ptr開銷較大。
  • 線程安全: shared_ptr是線程安全的,unique_ptr不是。
  • 循環引用: 是否需要處理循環引用問題?

為什么推薦使用make_shared?

std::make_shared是一個模板函數,用于創建一個shared_ptr并初始化其指向的對象。與先new再用shared_ptr包裝相比,make_shared的主要優勢在于:

  • 異常安全: 如果new操作成功,但在shared_ptr構造函數中拋出異常,會導致內存泄漏。make_shared將對象的內存分配和shared_ptr的構造放在一起,避免了這個問題。
  • 性能優化 make_shared通常會一次性分配對象和shared_ptr控制塊的內存,減少內存分配次數,提高性能。

智能指針與原始指針的混合使用問題

雖然智能指針旨在替代原始指針,但在某些情況下,仍然需要與原始指針交互,例如:

  • 與舊代碼庫集成: 舊代碼庫可能使用原始指針,需要將智能指針轉換為原始指針進行交互。
  • 底層操作: 某些底層操作可能需要直接訪問內存地址。

在與原始指針交互時,需要格外小心,避免出現以下問題:

  • 重復釋放: 不要使用原始指針delete由智能指針管理的對象。
  • 懸掛指針: 當智能指針釋放對象后,原始指針可能變成懸掛指針。

可以使用get()方法從智能指針獲取原始指針,但必須謹慎使用,并確保在使用原始指針時,智能指針仍然有效。

如何避免循環引用?

循環引用是指兩個或多個對象相互持有shared_ptr,導致引用計數永遠不為零,對象無法被釋放。

例如:

#include <iostream> #include <memory>  class A; // 前向聲明  class B { public:     std::shared_ptr<A> a;     ~B() { std::cout << "B destructedn"; } };  class A { public:     std::shared_ptr<B> b;     ~A() { std::cout << "A destructedn"; } };  int main() {     std::shared_ptr<A> a = std::make_shared<A>();     std::shared_ptr<B> b = std::make_shared<B>();      a->b = b;     b->a = a; // 循環引用      // a 和 b 都不會被銷毀,導致內存泄漏      return 0; }

解決循環引用的常用方法是使用weak_ptr。將其中一個shared_ptr改為weak_ptr,打破循環引用。

#include <iostream> #include <memory>  class A; // 前向聲明  class B { public:     std::weak_ptr<A> a; // 使用 weak_ptr     ~B() { std::cout << "B destructedn"; } };  class A { public:     std::shared_ptr<B> b;     ~A() { std::cout << "A destructedn"; } };  int main() {     std::shared_ptr<A> a = std::make_shared<A>();     std::shared_ptr<B> b = std::make_shared<B>();      a->b = b;     b->a = a; // 循環引用被打破      // a 和 b 都會被銷毀      return 0; }

智能指針在多線程環境下的使用

shared_ptr本身是線程安全的,多個線程可以同時訪問和修改同一個shared_ptr對象,而不會導致數據競爭。但是,shared_ptr指向的對象本身并不一定是線程安全的,需要額外的同步機制來保護對象的訪問。

unique_ptr不是線程安全的,不應該在多個線程之間共享。如果需要在多線程之間傳遞所有權,可以使用std::move顯式地轉移所有權。

weak_ptr本身也是線程安全的,但需要注意,在將weak_ptr轉換為shared_ptr時,可能會出現競爭條件。

自定義刪除器

智能指針允許使用自定義刪除器,用于在對象被銷毀時執行特定的操作。自定義刪除器可以是一個函數、函數對象或Lambda表達式。

#include <iostream> #include <memory>  void customDeleter(int* ptr) {     std::cout << "Custom deleter calledn";     delete ptr; }  int main() {     std::unique_ptr<int, void(*)(int*)> ptr(new int(10), customDeleter);      // 或者使用 lambda 表達式     std::shared_ptr<int> sharedPtr(new int(20), [](int* ptr) {         std::cout << "Lambda deleter calledn";         delete ptr;     });      return 0; }

自定義刪除器的主要用途:

  • 釋放非內存資源: 例如文件句柄、網絡連接等。
  • 執行特定的清理操作: 例如關閉數據庫連接、釋放鎖等。
  • 與C風格API集成: 當需要使用C風格的釋放函數時。

智能指針的性能考量

雖然智能指針提供了很多便利,但也帶來了一些性能開銷:

  • 引用計數: shared_ptr需要維護引用計數,增加了一些額外的開銷。
  • 虛函數表: 如果使用自定義刪除器,可能會增加虛函數表的開銷。
  • 內存分配: shared_ptr的控制塊和對象可能需要分別分配內存。

在性能敏感的場景下,需要仔細評估智能指針的性能影響,并選擇合適的智能指針類型。在不需要共享所有權的情況下,優先使用unique_ptr。

總結

C++智能指針是管理動態內存的強大工具,可以有效地避免內存泄漏和懸掛指針。理解不同類型的智能指針的特點和適用場景,可以編寫更安全、更可靠的C++代碼。記住,選擇合適的智能指針是關鍵,并且需要謹慎處理與原始指針的交互,以避免潛在的問題。

? 版權聲明
THE END
喜歡就支持一下吧
點贊9 分享