1. 程式人生 > >C++ 筆記:動態記憶體和智慧指標

C++ 筆記:動態記憶體和智慧指標

  • 物件的生存期:
    • 全域性物件:程式啟動時建立,程式結束時銷燬
    • 區域性自動物件:進入物件所在程式塊時建立,離開程式塊時銷燬
    • static 物件:第一次使用時建立,程式結束時銷燬
    • 動態分配的物件:建立之後只有在顯式地被釋放時才會銷燬
      • 每個程式擁有一個記憶體池,稱為自由空間或,用來儲存動態分配的物件

智慧指標

  • 傳統的動態記憶體管理:
    • new:在動態記憶體中為物件分配空間,返回一個指向該物件的指標
    • delete:接受一個動態物件的指標,銷燬該物件,釋放記憶體
  • 智慧指標:
    • #include <memory>
    • 智慧指標是模板類,建立智慧指標時需要提供具體的型別
    • shared_ptr
      :允許多個指標指向同一個物件
    • unique_ptr:“獨佔”所指向的物件
    • shared_ptrunique_ptr 都支援的操作
shared_ptr<T> sp_T;       // 空智慧指標
unique_ptr<T> up_T;
*sp_T, *up_T              // 解引用
sp_T->mem, up_T->mem      // 成員訪問
swap(p1, p2), p1.swap(p2) // 交換 p1 和 p2 

shared_ptr

shared_ptr<int> sp_int;
shared_ptr<
vector<string> sp_str_vec;

shared_ptr 獨有的操作

make_shared<T>(args)    // 返回一個 shared_ptr,指向一個動態分配的 T 型別物件,並使用 args 初始化該物件
shared_ptr<T>sp2(sp1)   // sp2 為 sp1 的拷貝;遞增 sp1 指向的物件的計數器
sp2 = sp1               // 遞減 sp2 原來所指向的物件的引用計數,遞增 sp1 指向的物件的引用計數;
                        // 若 sp2 原來所指向的物件的引用計數變為 0,則釋放該物件
sp.unique() // 若 sp.use_count() 為 1,則返回 true,否則返回 false sp.use_count() // 返回與 sp 共享物件的智慧指標數量

make_shared<T>(args)

  • 最安全的分配和使用動態記憶體的方法
  • 在記憶體中分配一個 T 型別的物件,使用 args 初始化該物件,並返回指向該物件的 shared_ptr
shared_ptr<int> sp_int = make_shared<int>(1024);
shared_ptr<string> sp_str = make_shared<string>("Hello, world!");
shared_ptr<vector<double>> sp_dbl_vec = make_shared<vector<double>>(vector<double>({3.14159, 1.414, 1.732}));
// 也可以使用 auto
list<int> int_lst{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto sp_int_lst = make_shared<list<int>>(int_lst);

shared_ptr 的賦值和拷貝

  • 進行賦值和拷貝時,每個 shared_ptr 都會記錄指向相同物件的其他 shared_ptr 的數量
  • 每進行一次賦值和拷貝,shared_ptr 指向的物件的引用計數都會遞增
  • 當給 shared_ptr 賦予一個新值,或是 shared_ptr 被銷燬時,其原來所指向的物件的引用計數會遞減
  • 當一個 shared_ptr 指向的物件的引用計數變為 0 時,便自動釋放該物件
auto sp_int1 = make_shared<int>(1024);    // sp_int1 指向的物件只有 sp_int1 一個引用者
auto sp_int2 = sp_int1;                   // sp_int1 指向的物件有了兩個引用者 sp_int1 和 sp_int2

使用 shared_ptr 實現多個物件之間共享資料

  • 一個物件的成員在物件銷燬時也會被銷燬;將資料儲存在動態記憶體中,可以實現物件銷燬時的資料保留
class StrBlob {
public:
  typedef vector<string>::size_type size_type;

  // constructors and destructor
  StrBlob();
  StrBlob(initializer_list<string> str_ilist);

  size_type size() const { return data->size(); }
  bool empty() const { return data->empty(); }
  
  void push_back(const string &str) { data->push_back(str); }
  void pop_back();

  string & front();
  string & back();

  shared_ptr<vector<string>> data;

private:
  void check(size_type i, const string & msg) const;
};

// 預設建構函式,在記憶體中分配一個空的 vector<string>
StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}

// 建構函式,在記憶體中分配所需的空間
StrBlob::StrBlob(initializer_list<string> str_ilist) : data(make_shared<vector<string>>(str_ilist)) {}

void StrBlob::check(size_type i, const string & msg) const {
  if (i >= data->size())
    throw out_of_range(msg);
}

string & StrBlob::front() {
  check(0, "front on empty StrBlob");
  return data->front();
}

string & StrBlob::back() {
  check(0, "front on empty StrBlob");
  return data->back();
}

void StrBlob::pop_back() {
  check(0, "pop_back on empty StrBlob");
  data->pop_back();
}


int main()
{
  StrBlob strblob1;

  {
    StrBlob strblob2({ "Hello, ", "welcome ", "to ", "the ", "C++ ", "world." });
    cout << strblob2.data << " : " << strblob2.front() << endl;   // output: 0328E30C : Hello,
    strblob1 = strblob2;
    cout << strblob1.data << " : " << strblob1.front() << endl;   // output: 0328E30C : Hello,
  }   // strblob2 被銷燬,但 strblob2.data 原來指向的記憶體空間依然有效,並被 strblob1.data 所指

  cout << strblob1.data << " : " << strblob1.front() << endl;     // output: 0328E30C : Hello,

  cin.get();
  return 0;
}

unique_ptr

  • 一個 unique_ptr “獨佔”它所指向的記憶體,也就是說,在任一時刻,最多隻能有一個 unique_ptr 指向一個給定物件
  • 當一個 unique_ptr 被銷燬時,其所指的物件也被銷燬

unique_ptr 獨有的操作

unique_ptr<T> up1;        // 空指標,可以指向 T 型別的物件
unique_ptr<T, D> up2;     // up1 使用 delete 釋放,up2 則使用型別為 D 的可呼叫物件釋放
unique_ptr<T, D> up3(d);  // up3 使用型別為 D 的物件 d 釋放

up = nullptr;             // 釋放 up 指向的物件,並將 up 置空
up.release();             // up 放棄對指標的控制權,返回指標,並將 up 置空
up.reset();               // 釋放 up 指向的物件
up.reset(q);              // 如果提供了內建指標 q,則令 up 指向這個物件;否則將 up 置空
up.reset(nullptr);
  • 定義一個 unique_ptr 時,需要將其繫結到一個 new 返回的指標
  • unique_ptr 不支援普通的賦值和拷貝操作
unique_ptr<double> up_dbl;
unique_ptr<int> up_int(new int(1024));
unique_ptr<string> up_str1(new string("Hello, world!"));
unique_ptr<vector<string>> up_str_vec(new vector<string>({ "Hello, ", "world!" }));

unique_ptr<string> up_str2(up_str1.release());  // 將所有權從 up_str1 轉移給 up_str2,
                                                // release() 將 up_str1 置空
unique_ptr<string> up_str3(new string("Welcome to the C++ world."));
up_str2.reset(up_str3.release());               // 將所有權從 up_str3 轉移給 up_str2,
                                                // 之後 reset() 將 up_str2 釋放