1. 程式人生 > >Boost學習系列2-智慧指標(下)

Boost學習系列2-智慧指標(下)

 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指標了,程式碼同上面的例子。