前面的几种智能指针在不同场合可以独立使用,然而,弱指针只有在配合共享指针使用时才会有意义,见下面例子:
#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指针了,代码同上面的例子。