瞭解 C++ 中的智慧指標

Mohd Mohtashim Nawaz 2023年10月12日
  1. 用 C++ 實現智慧指標
  2. 使用智慧指標解構函式防止 C++ 中的記憶體洩漏
  3. C++ 中智慧指標的型別
  4. 何時在 C++ 中使用智慧指標
瞭解 C++ 中的智慧指標

本文將討論 C++ 中的智慧指標、它們如何防止記憶體洩漏、智慧指標的型別以及我們應該使用它們的情況。

用 C++ 實現智慧指標

我們可以將 C++ 中的智慧指標定義為一個類别範本,我們可以使用它來維護原始原始指標。我們的智慧指標類包含一個指標變數來儲存我們的原始指標、解構函式和運算子覆蓋方法。

但是,我們不限於僅包括這些欄位和方法。我們可以根據需要新增我們的方法。

我們使用這個類來處理指標,而不是直接處理原始指標。除了在 C++ 中為智慧指標定義自定義類,我們還可以使用標準 C++ 庫。

讓我們為 C++ 智慧指標實現定義一個自定義類。

#include <iostream>

using namespace std;

template <class T>
class CustomSmartPointer {
  T *data;

 public:
  explicit CustomSmartPointer(T *ptr = NULL)  // Constructor to assign pointer
  {
    data = ptr;
  }
  ~CustomSmartPointer()  // Destructor that deallocated memory
  {
    delete data;
  }
  // We overload * and -> operators
  T *operator->() { return data; }
  T &operator*() { return *data; }
};

int main() {
  CustomSmartPointer<int> myPtr(new int());
  *myPtr = 100;
  cout << *myPtr << endl;
  // After executing above statement, memory allocated to myPtr is deallocated.
  return 0;
}

輸出:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ custom_smart_prt.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
100

使用智慧指標解構函式防止 C++ 中的記憶體洩漏

解構函式的存在使我們的智慧指標類與眾不同,並將其與原始指標區分開來。我們使用 C++ 智慧指標解構函式來釋放分配給我們指標的記憶體。

當類物件超出範圍時,會自動呼叫解構函式並釋放分配的記憶體。如果我們已經自動化了記憶體釋放過程,我們就不必擔心我們可能忘記釋放的指標導致的資源洩漏。

智慧指標解構函式的作用類似於 C++ 中的自動垃圾收集器,類似於 Java 和 C#。

讓我們看一個由原始指標引起的資源洩漏的例子。

#include <iostream>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  while (true) {
    student* p = new student;
    *p->name = "Stark";
    *p->marks = 100;
  }
  return 0;
}

如我們所見,我們正在無限迴圈中初始化學生型別的指標。我們還沒有釋放指標記憶體,所以這將繼續分配資源,直到所有記憶體都被佔用。

我們不應該長時間執行這個程式。否則,我們的系統可能會因為記憶體溢位而掛起。

但是,如果我們使用智慧指標,記憶體將在每次迴圈後自動釋放。因此,一次只佔用一個指標記憶體。

C++ 中智慧指標的型別

我們在 C++ 中有三種不同型別的智慧指標。這些是在 C++ 庫中為智慧指標實現的型別。

unique_ptr

這種智慧指標型別讓我們只為底層原始指標分配一個使用者。這意味著我們不能將相同的底層指標分配給兩個或更多物件。

我們應該預設使用這種型別的指標,直到我們可以共享記憶體。

shared_ptr

顧名思義,我們可以使用這個智慧指標將多個所有者分配給一個原始指標。這使用參考計數器來跟蹤受讓人的數量。

weak_ptr

這種智慧指標型別與 shared_ptr 非常相似,只是它不參與引用計數器。這並不意味著它沒有引用計數器,但它的引用不被計數。

相反,我們使用它的引用計數器來計算其他共享引用。當我們不需要嚴格控制所有權時,我們可以使用這個指標。

它提供對一個或多個 shared_ptr 物件的訪問。但是,它有一個優勢,因為它消除了死鎖的可能性。

讓我們看一個 C++ 智慧指標實現。

#include <iostream>
#include <memory>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  unique_ptr<student> ptr1(new student);
  ptr1->setName("Stark");
  ptr1->setMarks(100);
  cout << "unique_ptr output >>\n";
  cout << ptr1->getName() << " : " << ptr1->getMarks() << "\n";

  cout << "shared_ptr output >> \n";
  shared_ptr<student> ptr2(new student);
  ptr2->setName("Tony");
  ptr2->setMarks(99);
  cout << ptr2->getName() << " : " << ptr2->getMarks() << "\n";

  shared_ptr<student> ptr22;
  ptr22 = ptr2;
  cout << ptr22->getName() << " : " << ptr22->getMarks() << "\n";
  cout << "Reference count of shared_ptr: " << ptr2.use_count() << endl;

  auto ptr = make_shared<student>();
  ptr->setName("Jarvis");
  ptr->setMarks(98);

  cout << "Weak pointer output >>" << endl;
  weak_ptr<student> ptr3;
  ptr3 = ptr;
  cout << "Reference count of weak_ptr: " << ptr3.use_count() << endl;
  shared_ptr<student> ref = ptr3.lock();
  cout << ref->getName() << " : " << ref->getMarks() << "\n";

  return 0;
}

輸出:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ types_smart.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
unique_ptr output >>
Stark : 100
shared_ptr output >>
Tony : 99
Tony : 99
Reference count of shared_ptr: 2
Weak pointer output >>
Reference count of weak_ptr: 1
Jarvis : 98

需要注意的是,弱指標的引用計數是 1,因為它不參與引用計數。

何時在 C++ 中使用智慧指標

我們不應該對所有事情都使用智慧指標。由於我們為智慧指標使用類别範本,它們通常會降低效能並增加程式的複雜性。

我們應該將原始指標用於效能關鍵的應用程式和小程式作為最佳實踐。但是,我們應該始終在指標資源使用結束後釋放它們。

如果我們的程式碼很大並且很難手動為每個指標釋放記憶體,我們應該使用智慧指標。如果應用程式不是效能關鍵,我們應該更喜歡使用智慧指標。

如果我們需要處理異常、引用計數、釋放時間執行,我們應該使用智慧指標。

相關文章 - C++ Pointer