在C++中,我們通過new(在動態記憶體中為物件分配空間並初始化物件)和delete(銷燬該物件,並釋放記憶體)直接分配和釋放動態記憶體。
如下程式碼:
1 |
int *pi = new int;//pi 指向一個未初始化的int |
有些人有這樣的疑問,指標一定要new嗎?其實指標和new沒有什麼關係。這裡的new在動態記憶體裡為物件分配了記憶體空間,並返回了一個指向該物件的指標。new是申請堆空間,不是在棧上分配記憶體,指標只要指向有效的記憶體空間就可以。比如:
1 2 3 |
int i; int *p = &i; //p可以直接使用了 |
new直接初始化對
象:
1 2 |
int *pi = new int(128);//pi指向值為128的物件 string *ps = new string("christian");//*ps 指向“christian”的字串 |
new分配const物件必須進行初始化,並且返回的是一個指向const物件的指標:
1 |
const int *p = new const int(1024);//分配並初始化一個const int; |
當然new申請記憶體分配的時候也不是都會成功的,一旦一個程式用光了他的所有可用記憶體(雖然這種情況一般很少發生),new表示式就會失敗。這時候會丟擲bad_alloc異常。所以我們需要通過delete來釋放佔用的記憶體。在這裡注意delete並不是要刪除指標,而是釋放指標所指的記憶體。
1 2 3 4 5 6 7 |
int i; int *pi = &i; string str = "dwarves"; double *pd = new double(33); delete str; // 錯誤:str不是一個指標 delete pi; // 錯誤:pi指向一個區域性變數 delete pd; // 正確 |
使用new和delete來管理動態記憶體常出的一些錯誤:
1.忘記delete,即導致了“記憶體洩漏”,
2.野指標。在物件已經被釋放掉之後,(這裡注意,此時的指標成為了懸垂指標,即指向曾經存在的物件,但該物件已經不再存在。結果未定義,而且難以檢測。)這時候我們再次使用,會產生使用非法記憶體的指標。
不過如果我們需要保留指標,可以在delete以後將nullptr賦予指標,這樣指標就不指向任何物件了,如下程式碼:
1 2 3 4 |
auto p(new auto 42); auto q = p; delete p; p = nullptr; |
題外話:在測試這個問題的時候,我輸出了下q的值發現還是42,並且沒有報錯,後來在delete p之後,我又給*p = 19;這個時候 p ,q的值在輸出的時候都是19,也沒有報錯。這個程式碼其實根本就是錯誤的了,因為p,q已經沒有有效的記憶體空間了。這裡是釋放了記憶體,但指標的值不變,指向的記憶體不會清0,指向的這片記憶體區域是待分配的,如果沒有被其他資料覆蓋的話,你就能幸運得輸出這主要原因是你分配的記憶體小,沒有繼續分配,被佔用的概率小所致。我用的xcode,換到VS下就正常報錯了,是因為VS為了從編譯器的角度上解決緩衝區溢位等問題,加上的這種功能,C++標準裡面沒有這麼要求,所有xcode和gcc是不會檢查的。所以在這裡 建議大家寫純C++程式碼的時候用vs。
3.重複delete,就會使自由空間遭到破壞如:
1 2 3 |
string *ps1 = new string ("one"),*ps2 = ps1; delete ps1; delete ps2;//ps2的記憶體已經被釋放了 |
雖然顯示的管理記憶體在效能上有一定的優勢,但是隨著多執行緒程式的出現和廣泛使用,記憶體管理不佳的的情況變得更嚴重。所以C++標準庫中的智慧指標很好的解決了這些問題。
auto_ptr以物件的方式管理堆分配的記憶體,並在適當的時間(比如析構),釋放記憶體。我們只需要將new操作返回的指標作為auto_ptr的初始值,而不需要呼叫delete:
1 |
auto_ptr (new int); |
但是auto_ptr在拷貝時會返回一個左值並且不能呼叫delete[];所以在C++11中改用shared_ptr(允許多個指標指向一個物件),unique_ptr(“獨佔”所指向的物件)還有weak_ptr它是一種不控制所指物件生存期的智慧指標,指向shared_ptr所管理的對像,在memory標頭檔案中。
1 |
shared_ptr |
如下程式碼:
1 |
shared_ptr<int> pi;//指向int |
當然我們也可以shared_ptr和new來結合使用,但是必須使用直接初始化的形式來初始化一個智慧指標,
1 2 |
shared_ptr<int> p1 = new int(1024);//error:必須使用直接初始化的形式 shared_ptr<int> p2(new int(1024)); |
但是最好不要混合使用普通指標和智慧指標,最安全的分配和使用動態記憶體的方法是呼叫make_shared的標準庫函式。在使用它的時候,必須指定想要建立的物件型別。
1 2 |
shared_ptr<int >pi = make_shared<int>(1);//指向一個值為1的int的shared_ptr shared_ptr<string>ps = make_shared<string>(10,'a');//ps為指向“aaaaaaaaaa”的string |
如果我們不傳遞任何引數,物件會進行值初始化
1 |
shared_ptr<int>pi = make_shared<int>();//初始化預設值為0; |
shared_ptr 實現了引用計數型的智慧指標,當進行拷貝的時候,計數器都會遞增。而對一個物件進行賦值時,賦值操作符減少左運算元所指物件的引用計數(減1,如果引用計數為減至0,則刪除物件),並增加右運算元所指物件的引用計數(加1),如下程式碼:
1 2 3 4 |
auto p = make_shared<int>(13);//p 指向的物件只有p一個引用者 auto q(p);//此時物件有兩個引用者 auto r = make_shared<int>(10); r = q; |
此時r的引用技術為0,r原指物件被自動釋放。q的引用計數增加。
weak_ptr的使用和析構都不會改變shared_ptr的引用計數。weak_ptr可以使用一個非常重要的成員函式lock()從被觀測的shared_ptr獲得一個可用的shared_ptr物件, 從而操作資源。但當expired()==true的時候,lock()函式將返回一個儲存空指標的shared_ptr.如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <boost/smart_ptr.hpp> #include <boost/make_shared.hpp> using namespace boost; using namespace std; int _tmain(int argc, _TCHAR* argv[]) { shared_ptr<int> sp(new int(10)); assert(sp.use_count() == 1); weak_ptr<int> wp(sp); //從shared_ptr建立weak_ptr assert(wp.use_count() == 1); if (!wp.expired())//判斷weak_ptr觀察的物件是否失效 { shared_ptr<int> sp2 = wp.lock();//獲得一個shared_ptr *sp2 = 100; assert(wp.use_count() == 2); } assert(wp.use_count() == 1); return 0; } |
當我們定義一個unique_ptr的時候,需要將其繫結到一個new返回的指標。
只能有一個uniqu_ptr指向物件,也就是說它不能被拷貝,也不支援賦值。但是我們可以通過move來移動
1 2 3 4 5 6 |
std::unique_ptr<int> p1(new int(5)); std::unique_ptr<int> p2 = p1; // 編譯會出錯 std::unique_ptr<int> p3 = std::move(p1); // 轉移所有權, 現在那塊記憶體歸p3所有, p1成為無效的指標. p3.reset(); //釋放記憶體. p1.reset(); //實際上什麼都沒做. |