shared_ptr是包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确的删除,它实现的是引用计数型的智能指针,可以被自由的拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0时)它时才可以被删除。
shared_ptr可以被安全的放到标准容器中,并弥补了auto_ptr因为转移语义而不能把指针作为STL容器元素的缺陷。
何时我们需要智能指针? |
有三种典型的情况适合使用智能指针:
共享所有权是指两个或多个对象需要同时使用第三个对象的情况。这第三个对象应该如何(或者说何时)被释放?为了确保释放的时机是正确的,每个使用这个共享资源的对象必须互相知道对方,才能准确掌握资源的释放时间。从设计或维护的观点来看,这种耦合是不可行的。更好的方法是让这些资源所有者将资源的生存期管理责任委派给一个智能指针。当没有共享者存在时,智能指针就可以安全地释放这个资源了。 异常安全,简单地说就是在异常抛出时没有资源泄漏并保证程序状态的一致性。如果一个对象是动态分配的,当异常抛出时它不会被删除。由于栈展开以及指针离开作用域,资源可以会泄漏直至程序结束(即使是程序结束时的资源回收也不是语言所保证的)。不仅可能程序会由于内存泄漏而耗尽资源,程序的状态也可能变得混乱。智能指针可以自动地为你释放这些资源,即使是在异常发生的情况下。 避免常见的错误。忘记调用 delete 是书本中最古老的错误(至少在这本书中)。一个智能指针不关心程序中的控制路径;它只关心在它所指向的对象的生存期结束时删除它。使用智能指针,你不再需要知道何时删除对象。并且,智能指针隐藏了释放资源的细节,因此使用者不需要知道是否要调用 安全和高效的智能指针是程序员的军火库中重要的武器。虽然C++标准库中提供了 std::auto_ptr, 但是它不能完全满足我们对智能指针的需求。例如,auto_ptr不能用作STL容器的元素。Boost的智能指针类填充了标准所留下来的缺口。
头文件: "boost/shared_ptr.hpp" shared_ptr 可以从一个裸指针、另一个shared_ptr、一个std::auto_ptr、或者一个boost::weak_ptr构造。还可以传递第二个参数给shared_ptr的构造函数,它被称为删除器(deleter)。删除器稍后会被调用,来处理共享资源的释放。这对于管理那些不是用new分配也不是用delete释放的资源时非常有用(稍后将看到创建客户化删除器的例子)。shared_ptr被创建后,它就可象普通指针一样使用了,除了一点,它不能被显式地删除。 shared_ptr的部分摘要: namespace boost { template<typename T> class shared_ptr { public: template <class Y> explicit shared_ptr(Y* p); template <class Y,class D> shared_ptr(Y* p,D d); ~shared_ptr(); shared_ptr(const shared_ptr & r); template <class Y> explicit shared_ptr(const weak_ptr<Y>& r); template <class Y> explicit shared_ptr(std::auto_ptr<Y>& r); shared_ptr& operator=(const shared_ptr& r); void reset(); T& operator*() const; T* operator->() const; T* get() const; bool unique() const; long use_count() const; operator unspecified_bool_type() const; //译注:原文是unspecified-bool-type(),有误 void swap(shared_ptr<T>& b); }; template <class T,class U> shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r); } 成员函数template <class Y> explicit shared_ptr(Y* p); 这个构造函数获得给定指针p的所有权。参数 p 必须是指向 Y 的有效指针。构造后引用计数设为1。唯一从这个构造函数抛出的异常是std::bad_alloc (仅在一种很罕见的情况下发生,即不能获得引用计数器所需的自由空间)。 template <class Y,class D> shared_ptr(Y* p,D d); 这个构造函数带有两个参数。第一个是shared_ptr将要获得所有权的那个资源,第二个是shared_ptr被销毁时负责释放资源的一个对象,被保存的资源将以d(p)的形式传给那个对象。因此p的值是否有效取决于d。如果引用计数器不能分配成功,shared_ptr抛出一个类型为std::bad_alloc的异常。 shared_ptr(const shared_ptr& r); r中保存的资源被新构造的shared_ptr所共享,引用计数加一。这个构造函数不会抛出异常。 template <class Y> explicit shared_ptr(const weak_ptr<Y>& r); 从一个weak_ptr (本章稍后会介绍)构造shared_ptr。这使得weak_ptr的使用具有线程安全性,因为指向weak_ptr参数的共享资源的引用计数将会自增(weak_ptr不影响共享资源的引用计数)。如果weak_ptr为空 template <typename Y> shared_ptr(std::auto_ptr<Y>& r); 这个构造函数从一个auto_ptr获取r中保存的指针的所有权,方法是保存指针的一份拷贝并对auto_ptr调用release。构造后的引用计数为1。而r当然就变为空的。如果引用计数器不能分配成功,则抛出 ~shared_ptr(); shared_ptr析构函数对引用计数减一。如果计数为零,则保存的指针被删除。删除指针的方法是调用operator delete 或者,如果给定了一个执行删除操作的客户化删除器对象,就把保存的指针作为唯一参数调用这个对象。析构函数不会抛出异常。 shared_ptr& operator=(const shared_ptr& r); 赋值操作共享r中的资源,并停止对原有资源的共享。赋值操作不会抛出异常。 void reset(); reset函数用于停止对保存指针的所有权的共享。共享资源的引用计数减一。 T& operator*() const; 这个操作符返回对已存指针所指向的对象的一个引用。如果指针为空,调用operator* 会导致未定义行为。这个操作符不会抛出异常。 T* operator->() const; 这个操作符返回保存的指针。这个操作符与operator*一起使得智能指针看起来象普通指针。这个操作符不会抛出异常。 T* get() const; get函数是当保存的指针有可能为空时(这时 operator* 和 operator-> 都会导致未定义行为)获取它的最好办法。注意,你也可以使用隐式布尔类型转换来测试 bool unique() const; 这个函数在shared_ptr是它所保存指针的唯一拥有者时返回 true ;否则返回 long use_count() const; use_count 函数返回指针的引用计数。它在调试的时候特别有用,因为它可以在程序执行的关键点获得引用计数的快照。小心地使用它,因为在某些可能的shared_ptr实现中,计算引用计数可能是昂贵的,甚至是不行的。这个函数不会抛出异常。 operator unspecified-bool-type() const; 这是个到unspecified-bool-type类型的隐式转换函数,它可以在Boolean上下文中测试一个智能指针。如果shared_ptr保存着一个有效的指针,返回值为True;否则为false。注意,转换函数返回的类型是不确定的。把返回类型当成bool用会导致一些荒谬的操作,所以典型的实现采用了safe bool idiom,[8]
void swap(shared_ptr<T>& b); 这可以很方便地交换两个shared_ptr。swap 函数交换保存的指针(以及它们的引用计数)。这个函数不会抛出异常。 普通函数template <typename T,typename U> shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r); 要对保存在shared_ptr里的指针执行static_cast,我们可以取出指针然后强制转换它,但我们不能把它存到另一个shared_ptr里;新的 用法使用shared_ptr解决的主要问题是知道删除一个被多个客户共享的资源的正确时机。下面是一个简单易懂的例子,有两个类 #include "boost/shared_ptr.hpp" #include <cassert> class A { boost::shared_ptr<int> no_; public: A(boost::shared_ptr<int> no) : no_(no) {} void value(int i) { *no_=i; } }; class B { boost::shared_ptr<int> no_; public: B(boost::shared_ptr<int> no) : no_(no) {} int value() const { return *no_; } }; int main() { boost::shared_ptr<int> temp(new int(14)); A a(temp); B b(temp); a.value(28); assert(b.value()==28); } 类 A 和 B都保存了一个 shared_ptr<int>. 在创建 回顾Pimpl(桥接模式)用法前一节展示了使用scoped_ptr的pimpl 用法,如果使用这种用法的类是不允许复制的,那么scoped_ptr在保存pimpl的动态分配实例时它工作得很好。但是这并不适合于所有想从pimpl用法中获益的类型(注意,你还可以用 shared_ptr 与标准库容器把对象直接存入容器中有时会有些麻烦。以值的方式保存对象意味着使用者将获得容器中的元素的拷贝,对于那些复制是一种昂贵的操作的类型来说可能会有性能的问题。此外,有些容器,特别是 有两种方式可以将shared_ptr应用于标准容器: 1:将容器作为shared_ptr管理的对象,如shared_ptr<list<T> >,使容器可以被安全的分享,用法与普通shared_ptr没有区别。 2:将shared_ptr作为容器的元素,如vector<shared_ptr<T> >. 下面是如何把共享指针存入标准库容器的例子。 #include "boost/shared_ptr.hpp" #include <vector> #include <iostream> class A { public: virtual void sing()=0; protected: virtual ~A() {}; }; class B : public A { public: virtual void sing() { std::cout << "Do re mi fa so la"; } }; boost::shared_ptr<A> createA() { boost::shared_ptr<A> p(new B()); return p; } int main() { typedef std::vector<boost::shared_ptr<A> > container_type; typedef container_type::iterator iterator; container_type container; for (int i=0;i<10;++i) { container.push_back(createA()); } std::cout << "The choir is gathered: \n"; iterator end=container.end(); for (iterator it=container.begin();it!=end;++it) { (*it)->sing(); } } 这里有两个类, A 和 B, 各有一个虚拟成员函数 sing. 上面的例子示范了一个强有力的技术,它涉及A里面的protected析构函数。因为函数 createA 返回的是 shared_ptr 与其它资源有时你会发现你要把shared_ptr用于某个特别的类型,它需要其它清除操作而不是简单的 delete. shared_ptr可以通过客户化删除器来支持这种需要。那些处理象 class FileCloser { public: void operator()(FILE* file) { std::cout << "The FileCloser has been called with a FILE*, " "which will now be closed.\n"; if (file!=0) fclose(file); } }; 这是一个函数对象,我们用它来确保在资源要释放时调用 fclose 。下面是使用FileCloser类的示例程序。 int main() { std::cout << "shared_ptr example with a custom deallocator.\n"; { FILE* f=fopen("test.txt","r"); if (f==0) { std::cout << "Unable to open file\n"; throw "Unable to open file"; } boost::shared_ptr<FILE> my_shared_file(f, FileCloser()); // 定位文件指针 fseek(my_shared_file.get(),42,SEEK_SET); } std::cout << "By now, the FILE has been closed!\n"; } 注意,在访问资源时,我们需要对shared_ptr使用 &* 用法, get, 或 { FILE* f=fopen("test.txt","r"); if (f==0) { std::cout << "Unable to open file\n"; throw file_exception(); } boost::shared_ptr<FILE> my_shared_file(f,&fclose); // 定位文件指针 fseek(&*my_shared_file,42,SEEK_SET); } std::cout << "By now, the FILE* has been closed!\n"; 定制删除器在处理需要特殊释放程序的资源时非常有用。由于删除器不是 shared_ptr 类型的一部分,所以使用者不需要知道关于智能指针所拥有的资源的任何信息(当然除了如何使用它!)。例如,你可以使用对象池,定制删除器只需简单地把对象返还到池中。或者,一个 singleton 对象应该使用一个什么都不做的删除器。 使用定制删除器的安全性我们已经看到对基类使用 protected 析构函数有助于增加使用shared_ptr的类的安全性。另一个达到同样安全级别的方法是,声明析构函数为 protected (或 private) 并使用一个定制删除器来负责销毁对象。这个定制删除器必须是它要删除的类的友元,这样它才可以工作。封装这个删除器的好方法是把它实现为私有的嵌套类,如下例所示: #include "boost/shared_ptr.hpp" #include <iostream> class A { class deleter { public: void operator()(A* p) { delete p; } }; friend class deleter; public: virtual void sing() { std::cout << "Lalalalalalalalalalala"; } static boost::shared_ptr<A> createA() { boost::shared_ptr<A> p(new A(),A::deleter()); return p; } protected: virtual ~A() {}; }; int main() { boost::shared_ptr<A> p=A::createA(); } 注意,我们在这里不能使用普通函数来作为 shared_ptr<A> 的工厂函数,因为嵌套的删除器是A私有的。使用这个方法,用户不可能在栈上创建 从this创建shared_ptr有时候,需要从this获得 shared_ptr ,即是说,你希望你的类被shared_ptr所管理,你需要把"自身"转换为shared_ptr的方法。看起来不可能?好的,解决方案来自于我们即将讨论的另一个智能指针boost::weak_ptr. weak_ptr 是 #include "boost/shared_ptr.hpp" #include "boost/enable_shared_from_this.hpp" class A; void do_stuff(boost::shared_ptr<A> p) { ... } class A : public boost::enable_shared_from_this<A> { public: void call_do_stuff() { do_stuff(shared_from_this()); } }; int main() { boost::shared_ptr<A> p(new A()); p->call_do_stuff(); } 这个例子还示范了你要用shared_ptr管理this的情形。类 A 有一个成员函数 总结引用计数智能指针是非常重要的工具。Boost的 shared_ptr 提供了坚固而灵活的解决方案,它已被广泛用于多种环境下。需要在使用者之间共享对象是常见的,而且通常没有办法通知使用者何时删除对象是安全的。shared_ptr 让使用者无需知道也在使用共享对象的其它对象,并让它们无需担心在没有对象引用时的资源释放。这对于Boost的智能指针类而言是最重要的。你会看到Boost.Smart_ptr中还有其它的智能指针,但这一个肯定是你最想要的。通过使用定制删除器,几乎所有资源类型都可以存入 在以下情况时使用 shared_ptr :
工厂函数 shared_ptr的构造还需要new调用,这导致了代码中的某种不对称性。虽然shared_ptr很好的包装了new表达式,但过多的显示new操作符也是个问题,它应该使用工厂模式解决。 shared_ptr在头文件<boost/make_shared.hpp>中提供了一个自由工厂函数(位于boost命名空间)make_shared<T>(),来消除显示的new调用,它的名字模仿了标准库的make_pair(),声明如下: template<class T,class...Args> shared_ptr<T>make_shared(Args && ... args); make_shared()函数可以接受最多10个参数,然后把它们传递给类型T的构造函数,创建一个shared_ptr<T>的对象并返回。make_shared()函数比直接创建shared_ptr对象的方式快且高效,因为它内部仅分配以此内存,消除了shared_ptr构造时的开销。 实例: #include<boost/make_sharde.hpp> int main(){ shared_ptr<string> sp = make_shared<string>("make_shared"); shared_ptr<vector<int> > spv = make_shared<vector<int> >(10,2); assert(spv->size()==10); } make_shared()不能接受任意多数量的参数构造对象,一般情况下这不会成为问题。实际上,很少有如此多的参数的函数接口,即使有,那也会是一个不够好的接口,应该被重构。 除了make_shared(),smart_ptr库还提供了一个allocate_shared(),它比make_shared()多接受一个定制的内存分配器类型参数,其他方面都相同。 |