Boost學習系列2-智慧指標(下)
前面的幾種智慧指標在不同場合可以獨立使用,然而,弱指標只有在配合共享指標使用時才會有意義,見下面例子:
#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指標了,程式碼同上面的例子。