new”是C++的一個關鍵字,同時也是操作符。關於new的話題非常多,因為它確實比較復雜,也非常神秘,下面我將把我了解到的與new有關的內容做一個總結。
1 class A 2 { 3 int i; 4 public: 5 A(int _i) :i(_i*_i) {} 6 void Say() { printf("i=%d/n", i); } 7 }; 8 //調用new: 9 A* pa = new A(3);
那么上述動態創建一個對象的過程大致相當於以下三句話(只是大致上):
1 A* pa = (A*)malloc(sizeof(A)); 2 pa->A::A(3); 3 return pa;
1 class A 2 { 3 public: 4 void* operator new(size_t size) 5 { 6 printf("operator new called/n"); 7 return ::operator new(size); 8 } 9 }; 10 11 A* a = new A();
這里通過::operator new調用了原有的全局的new,實現了在分配內存之前輸出一句話。全局的operator new也是可以重載的,但這樣一來就不能再遞歸的使用new來分配內存,而只能使用malloc了:
1 void* operator new(size_t size) 2 { 3 printf("global new/n"); 4 return malloc(size); 5 }
1 #include <new.h> 2 3 void main() 4 { 5 char s[sizeof(A)]; 6 A* p = (A*)s; 7 new(p) A(3); //p->A::A(3); 8 p->Say(); 9 }
對頭文件<new>或<new.h>的引用是必須的,這樣才可以使用placement new。這里“new(p) A(3)”這種奇怪的寫法便是placement new了,它實現了在指定內存地址上用指定類型的構造函數來構造一個對象的功能,后面A(3)就是對構造函數的顯式調用。這里不難發現,這塊指定的地址既可以是棧,又可以是堆,placement對此不加區分。但是,除非特別必要,不要直接使用placement new ,這畢竟不是用來構造對象的正式寫法,只不過是new operator的一個步驟而已。使用new operator地編譯器會自動生成對placement new的調用的代碼,因此也會相應的生成使用delete時調用析構函數的代碼。如果是像上面那樣在棧上使用了placement new,則必須手工調用析構函數,這也是顯式調用析構函數的唯一情況:
1 p->~A();
1 void* operator new(size_t size) 2 { 3 void* p = null 4 while(!(p = malloc(size))) 5 { 6 if(null == new_handler) 7 throw bad_alloc(); 8 try 9 { 10 new_handler(); 11 } 12 catch(bad_alloc e) 13 { 14 throw e; 15 } 16 catch(…) 17 {} 18 } 19 return p; 20 }
在默認情況下,new_handler的行為是拋出一個bad_alloc異常,因此上述循環只會執行一次。但如果我們不希望使用默認行為,可以自定義一個new_handler,並使用std::set_new_handler函數使其生效。在自定義的new_handler中,我們可以拋出異常,可以結束程序,也可以運行一些代碼使得有可能有內存被空閑出來,從而下一次分配時也許會成功,也可以通過set_new_handler來安裝另一個可能更有效的new_handler。例如:
1 void MyNewHandler() 2 { 3 printf(“New handler called!/n”); 4 throw std::bad_alloc(); 5 } 6 7 std::set_new_handler(MyNewHandler);
1 class SomeClass 2 { 3 static int count; 4 SomeClass() {} 5 public: 6 static SomeClass* GetNewInstance() 7 { 8 count++; 9 return new SomeClass(); 10 } 11 };
靜態變量count用於記錄此類型生成的實例的個數,在上述代碼中,如果因new分配內存失敗而拋出異常,那么其實例個數並沒有增加,但count變量的值卻已經多了一個,從而數據結構被破壞。正確的寫法是:
1 static SomeClass* GetNewInstance() 2 { 3 SomeClass* p = new SomeClass(); 4 count++; 5 return p; 6 }
這樣一來,如果new失敗則直接拋出異常,count的值不會增加。類似的,在處理線程同步時,也要注意類似的問題:
1 void SomeFunc() 2 { 3 lock(someMutex); //加一個鎖 4 delete p; 5 p = new SomeClass(); 6 unlock(someMutex); 7 }
1 template <class T1, class T2> 2 inline void construct(T1* p, const T2& value) 3 { 4 new(p) T1(value); 5 }
此函數接收一個已構造的對象,通過拷貝構造的方式在給定的內存地址p上構造一個新對象,代碼中后半截T1(value)便是placement new語法中調用構造函數的寫法,如果傳入的對象value正是所要求的類型T1,那么這里就相當於調用拷貝構造函數。類似的,因使用了placement new,編譯器不會自動產生調用析構函數的代碼,需要手工的實現:
1 template <class T> 2 inline void destory(T* pointer) 3 { 4 pointer->~T(); 5 }
與此同時,STL中還有一個接收兩個迭代器的destory版本,可將某容器上指定范圍內的對象全部銷毀。典型的實現方式就是通過一個循環來對此范圍內的對象逐一調用析構函數。如果所傳入的對象是非簡單類型,這樣做是必要的,但如果傳入的是簡單類型,或者根本沒有必要調用析構函數的自定義類型(例如只包含數個int成員的結構體),那么再逐一調用析構函數是沒有必要的,也浪費了時間。為此,STL使用了一種稱為“type traits”的技巧,在編譯器就判斷出所傳入的類型是否需要調用析構函數:
1 template <class ForwardIterator> 2 inline void destory(ForwardIterator first, ForwardIterator last) 3 { 4 __destory(first, last, value_type(first)); 5 }
其中value_type()用於取出迭代器所指向的對象的類型信息,於是:
1 template<class ForwardIterator, class T> 2 inline void __destory(ForwardIterator first, ForwardIterator last, T*) 3 { 4 typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; 5 __destory_aux(first, last, trivial_destructor()); 6 } 7 //如果需要調用析構函數: 8 template<class ForwardIterator> 9 inline void __destory_aux(ForwardIterator first, ForwardIterator last, __false_type) 10 { 11 for(; first < last; ++first) 12 destory(&*first); //因first是迭代器,*first取出其真正內容,然后再用&取地址 13 } 14 //如果不需要,就什么也不做: 15 tempalte<class ForwardIterator> 16 inline void __destory_aux(ForwardIterator first, ForwardIterator last, __true_type) 17 {}
因上述函數全都是inline的,所以多層的函數調用並不會對性能造成影響,最終編譯的結果根據具體的類型就只是一個for循環或者什么都沒有。這里的關鍵在於__type_traits<T>這個模板類上,它根據不同的T類型定義出不同的has_trivial_destructor的結果,如果T是簡單類型,就定義為__true_type類型,否則就定義為__false_type類型。其中__true_type、__false_type只不過是兩個沒有任何內容的類,對程序的執行結果沒有什么意義,但在編譯器看來它對模板如何特化就具有非常重要的指導意義了,正如上面代碼所示的那樣。__type_traits<T>也是特化了的一系列模板類:
1 struct __true_type {}; 2 struct __false_type {}; 3 template <class T> 4 struct __type_traits 5 { 6 public: 7 typedef __false _type has_trivial_destructor; 8 …… 9 }; 10 template<> //模板特化 11 struct __type_traits<int> //int的特化版本 12 { 13 public: 14 typedef __true_type has_trivial_destructor; 15 …… 16 }; 17 …… //其他簡單類型的特化版本
如果要把一個自定義的類型MyClass也定義為不調用析構函數,只需要相應的定義__type_traits<T>的一個特化版本即可:
1 template<> 2 struct __type_traits<MyClass> 3 { 4 public: 5 typedef __true_type has_trivial_destructor; 6 …… 7 };
1 char* s = new char[100]; 2 …… 3 delete s;
嚴格的說,上述代碼是不正確的,因為我們在分配內存時使用的是new[],而並不是簡單的new,但釋放內存時卻用的是delete。正確的寫法是使用delete[]:
1 delete[] s;
但是,上述錯誤的代碼似乎也能編譯執行,並不會帶來什么錯誤。事實上,new與new[]、delete與delete[]是有區別的,特別是當用來操作復雜類型時。假如針對一個我們自定義的類MyClass使用new[]:
1 MyClass* p = new MyClass[10];
1 delete[] p;
1 class MyClass 2 { 3 int a; 4 public: 5 MyClass() { printf("ctor/n"); } 6 ~MyClass() { printf("dtor/n"); } 7 }; 8 9 void* operator new[](size_t size) 10 { 11 void* p = operator new(size); 12 printf("calling new[] with size=%d address=%p/n", size, p); 13 return p; 14 } 15 16 // 主函數 17 MyClass* mc = new MyClass[3]; 18 printf("address of mc=%p/n", mc); 19 delete[] mc;
運行此段代碼,得到的結果為:(VC2005)
1 template <class T> 2 T* New[](int count) 3 { 4 int size = sizeof(T) * count + 4; 5 void* p = T::operator new[](size); 6 *(int*)p = count; 7 T* pt = (T*)((int)p + 4); 8 for(int i = 0; i < count; i++) 9 new(&pt[i]) T(); 10 return pt; 11 }
上述示意性的代碼省略了異常處理的部分,只是展示當我們對一個復雜類型使用new[]來動態分配數組時其真正的行為是什么,從中可以看到它分配了比預期多4個字節的內存並用它來保存對象的個數,然后對於后面每一塊空間使用placement new來調用無參構造函數,這也就解釋了為什么這種情況下類必須有無參構造函數,最后再將首地址返回。類似的,我們很容易寫出相應的delete[]的實現代碼:
1 template <class T> 2 void Delete[](T* pt) 3 { 4 int count = ((int*)pt)[-1]; 5 for(int i = 0; i < count; i++) 6 pt[i].~T(); 7 void* p = (void*)((int)pt – 4); 8 T::operator delete[](p); 9 }
1 char *p = 0; 2 for(int i = 0; i < 40; i += 4) 3 { 4 char* s = new char[i]; 5 printf("alloc %2d bytes, address=%p distance=%d/n", i, s, s - p); 6 p = s; 7 }

