3.5、弱指標
前面的幾種智能指標在不同場合可以獨立使用,然而,弱指標只有在配合共用指標使用時才會有意義,見下面例子:
#include <windows.h> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <iostream> DWORD WINAPI reset(LPVOID p) { boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p); sh->reset(); return 0; } DWORD WINAPI print(LPVOID p) { boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p); boost::shared_ptr<int> sh = w->lock(); if (sh) std::cout << *sh << std::endl; return 0; } int main() { boost::shared_ptr<int> sh(new int(99)); boost::weak_ptr<int> w(sh); HANDLE threads[2]; threads[0] = CreateThread(0, 0, reset, &sh, 0, 0); threads[1] = CreateThread(0, 0, print, &w, 0, 0); WaitForMultipleObjects(2, threads, TRUE, INFINITE); }
boost::wead_ptr總是通過boost::shared_ptr來初始化的,一旦初始化之後,它基本上只提供一個有用的方法: lock()。此方法返回的boost::shared_ptr與用來初始化弱指標的共用指標共用所有權。 如果這個共用指標不含有任何對象,返回的共用指標也將是空的。
當函數需要一個由共用指標所管理的對象,而這個對象的生存期又不依賴於這個函數時,就可以使用弱指標。 只要程式中還有一個共用指標掌管著這個對象,函數就可以使用該對象。 如果共用指標複位了,就算函數裡能得到一個共用指標,對象也不存在了。
第一個線程函數reset的參數是一個共用指標的地址。第二個線程函數print的參數是一個弱指標的地址。這個弱指標是之前通過共用指標初始化的。一旦程式啟動之後,reset和 print就都開始執行了。不過執行順序是不確定的,這就導致了一個潛在的問題:reset線程在銷毀對象的時候print線程可能正在訪問它。這時就可以通過調用弱指標的lock() 函數解決這個問題:如果對象存在,那麼lock()函數返回的共用指標指向這個合法的對象。否則,返回的共用指標被設定為0,這等價於標準的null指標。弱指標本身對於對象的生存期沒有任何影響。lock返回一個共用指標,print函數就可以安全的訪問對象了。這就保證了即使另一個線程要釋放對象,由於我們有返回的共用指標,對象依然存在。
3.6、介入式指標
其實介入式指標(boost::intrusive_ptr)的工作方式和共用指標幾乎一樣,boost::shared_ptr 在內部記錄著引用到某個對象的共用指標的數量,而對介入式指標,程式員就得自己來做記錄。對於架構對象來說這就特別有用,因為它們記錄著自身被引用的次數。
#include <boost/intrusive_ptr.hpp> #include <atlbase.h> #include <iostream> void intrusive_ptr_add_ref(IDispatch *p) { p->AddRef(); } void intrusive_ptr_release(IDispatch *p) { p->Release(); } void check_windows_folder() { CLSID clsid; CLSIDFromProgID(CComBSTR("Scripting.FileSystemObject"), &clsid); void *p; CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, __uuidof(IDispatch), &p); boost::intrusive_ptr<IDispatch> disp(static_cast<IDispatch*>(p)); CComDispatchDriver dd(disp.get()); CComVariant arg("C:\\Windows"); CComVariant ret(false); dd.Invoke1(CComBSTR("FolderExists"), &arg, &ret); std::cout << (ret.boolVal != 0) << std::endl; } void main() { CoInitialize(0); check_windows_folder(); CoUninitialize(); }
上面的例子中使用了COM(元件物件模型)提供的函數,所以只能在Windows平台上編譯運行。COM 物件是使用boost::intrusive_ptr的絕佳範例,因為COM對象需要記錄當前有多少指標引用著它。通過調用AddRef和Release函數,內部的引用計數分別增1或者減1。當引用計數為0時,COM對象自動銷毀。
在intrusive_ptr_add_ref和intrusive_ptr_release內部調用AddRef和Release這兩個函數,來增加或減少相應COM對象的引用計數。這個例子中用到的COM對象名為 'FileSystemObject',在Windows上它是預設可用的。通過這個對象可以訪問底層的檔案系統,比如檢查一個給定的目錄是否存在。在上例中,我們檢查C:\Windows目錄是否存在。具體它在內部是怎麼實現的,跟boost::intrusive_ptr的功能無關,完全取決於COM。關鍵點在於一旦介入式指標 disp 離開了它的範圍——check_windows_folder() 函數的末尾,函數 intrusive_ptr_release() 將會被自動調用。這將減少COM對象'FileSystemObject'的內部引用計數到0,於是該對象就銷毀了。
3.7、指標容器
在用C語言時,如果我們要建立一個動態數組,我們可以new一個,但是這樣使用十分麻煩,因此可以選擇更進階寫的容器vector,它可以很好的動態管理數組(push_back、erase等)。對應的,boost中也有這樣的指標:
#include <boost/ptr_container/ptr_vector.hpp> int main() { boost::ptr_vector<int> v; v.push_back(new int(1)); v.push_back(new int(2)); }
boost::ptr_vector專門用於動態分配的對象,它使用起來更容易也更高效。 boost::ptr_vector 獨佔它所包含的對象,因而容器之外的共用指標不能共用所有權,這跟 std::vector<boost::shared_ptr<int> > 相反。
除了boost::ptr_vector之外,專門用於管理動態指派至的容器還包括:boost::ptr_deque, boost::ptr_list,boost::ptr_set,boost::ptr_map,boost::ptr_unordered_set和 boost::ptr_unordered_map。這些容器等價於C++標準裡提供的那些。最後兩個容器對應於std::unordered_set和std::unordered_map,它們作為技術報告1的一部分加入C++標準。如果所使用的C++標準實現不支援技術報告1的話,還可以使用Boost C++庫裡實現的 boost::unordered_set和boost::unordered_map。
四、練習題:
最佳化下面兩個程式:
1、
#include <iostream> #include <cstring> char *get(const char *s) { int size = std::strlen(s); char *text = new char[size + 1]; std::strncpy(text, s, size + 1); return text; } void print(char *text) { std::cout << text << std::endl; } int main(int argc, char *argv[]) { if (argc < 2) { std::cerr << argv[0] << " <data>" << std::endl; return 1; } char *text = get(argv[1]); print(text); delete[] text; }
2、
#include <vector> template <typename T> T *create() { return new T; } int main() { std::vector<int*> v; v.push_back(create<int>()); }
五、解答
1、要最佳化這個程式,用範圍數組指標就可以了,它會自動調用delete[]析構
#include <boost/scoped_array.hpp>
char *get(const char *s)
{
int size = std::strlen(s);
boost::scoped_array<char> s(new char[size]);
std::strncpy(text, s, size + 1);
return text;
}
main函數裡的delete可以不要了,進而避免程式在delete調用之前退出導致的記憶體未釋放。
2、要最佳化它,當然是使用ptr_vector指標了,代碼同上面的例子。