boost智能指针
使用protected可以保护不让生成对象,如A a;会编译错误,但可以A *a = new A;
但此时如果delete a,也是会编译错误,所以总体来说还是错误的
protected:
virtual ~A() {};
shared_ptr不是线性安全的,因为存在一个引用计数.
而intrusive_ptr是线性安全的,因为它要求我们自己写引用计数函数
何时我们需要智能指针?
有三种典型的情况适合使用智能指针:
-
资源所有权的共享
-
要写异常安全的代码时
-
避免常见的错误,如资源泄漏
共享所有权是指两个或多个对象需要同时使用第三个对象的情况。这第三个对象应该如何(或者说何时)被释放?为了确保释放的时机是正确的,每个使用这个共享资源的对象必须互相知道对方,才能准确掌握资源的释放时间。从设计或维护的观点来看,这种耦合是不可行的。更好的方法是让这些资源所有者将资源的生存期管理责任委派给一个智能指针。当没有共享者存在时,智能指针就可以安全地释放这个资源了。
异常安全,简单地说就是在异常抛出时没有资源泄漏并保证程序状态的一致性。如果一个对象是动态分配的,当异常抛出时它不会被删除。由于栈展开以及指针离开作用域,资源可以会泄漏直至程序结束(即使是程序结束时的资源回收也不是语言所保证的)。不仅可能程序会由于内存泄漏而耗尽资源,程序的状态也可能变得混乱。智能指针可以自动地为你释放这些资源,即使是在异常发生的情况下。
避免常见的错误。忘记调用 delete 是书本中最古老的错误(至少在这本书中)。一个智能指针不关心程序中的控制路径;它只关心在它所指向的对象的生存期结束时删除它。使用智能指针,你不再需要知道何时删除对象。并且,智能指针隐藏了释放资源的细节,因此使用者不需要知道是否要调用 delete, 有些特殊的清除函数并不总是删除资源的。
安全和高效的智能指针是程序员的军火库中重要的武器。虽然C++标准库中提供了 std::auto_ptr, 但是它不能完全满足我们对智能指针的需求。例如,auto_ptr不能用作STL容器的元素。Boost的智能指针类填充了标准所留下来的缺口。
scoped_ptr
头文件: "boost/scoped_ptr.hpp"
boost::scoped_ptr用于确保能够正确地删除动态分配的对象。scoped_ptr有着与std::auto_ptr类似的特性,而最大的区别在于它不能转让所有权而auto_ptr可以。事实上,scoped_ptr永远不能被复制或被赋值!scoped_ptr拥有它所指向的资源的所有权,并永远不会放弃这个所有权。scoped_ptr的这种特性提升了我们的代码的表现,我们可以根据需要选择最合适的智能指针(scoped_ptr或 auto_ptr)。
要决定使用std::auto_ptr还是boost::scoped_ptr, 就要考虑转移所有权是不是你想要的智能指针的一个特性。如果不是,就用scoped_ptr. 它是一种轻量级的智能指针;使用它不会使你的程序变大或变慢。它只会让你的代码更安全,更好维护。
#include
"boost/scoped_ptr.hpp"
#include <string>
#include
<iostream>
int main() {
{
boost::scoped_ptr<std::string>
p(new std::string("Use
scoped_ptr often."));
// 打印字符串的值
if (p)
std::cout << *p << '\n';
//
获取字符串的大小
size_t i=p->size();
// 给字符串赋新值
*p="Acts just like a pointer";
} //
这里p被销毁,并删除std::string
}
由于scoped_ptr::get会返回一个裸指针,所以就有可能对scoped_ptr做一些有害的事情,其中有两件是你尤其要避免的。第一,不要删除这个裸指针。因为它会在scoped_ptr被销毁时再一次被删除。第二,不要把这个裸指针保存到另一个scoped_ptr (或其它任何的智能指针)里。因为这样也会两次删除这个指针,每个scoped_ptr一次。简单地说,尽量少用get, 除非你要使用那些要求你传送裸指针的遗留代码!
scoped_ptr不同于 const auto_ptr
留心的读者可能已经注意到auto_ptr可以几乎象scoped_ptr一样地工作,只要把auto_ptr声明为const:
const auto_ptr<A> no_transfer_of_ownership(new A);
它们很接近,但不是一样。最大的区别在于scoped_ptr可以被reset, 在需要时可以删除并替换被指物。而对于const auto_ptr这是不可能的。另一个小一点的区别是,它们的名字不同:尽管const auto_ptr意思上和scoped_ptr一样,但它更冗长,也更不明显。当你的词典里有了scoped_ptr,你就应该使用它,因为它可以更清楚地表明你的意图。如果你想说一个资源是要被限制在作用域里的,并且不应该有办法可以放弃它的所有权,你就应该用 boost::scoped_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);
}
shared_ptr与标准库容器
把对象直接存入容器中有时会有些麻烦。以值的方式保存对象意味着使用者将获得容器中的元素的拷贝,对于那些复制是一种昂贵的操作的类型来说可能会有性能的问题。此外,有些容器,特别是 std::vector, 当你加入元素时可能会复制所有元素,这更加重了性能的问题。最后,传值的语义意味着没有多态的行为。如果你需要在容器中存放多态的对象而且你不想切割它们,你必须用指针。如果你用裸指针,维护元素的完整性会非常复杂。从容器中删除元素时,你必须知道容器的使用者是否还在引用那些要删除的元素,不用担心多个使用者使用同一个元素。这些问题都可以用shared_ptr来解决。
下面是如何把共享指针存入标准库容器的例子。
#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();
}
}
shared_ptr与其它资源
有时你会发现你要把shared_ptr用于某个特别的类型,它需要其它清除操作而不是简单的 delete. shared_ptr可以通过客户化删除器来支持这种需要。那些处理象 FILE*这样的操作系统句柄的资源通常要使用象fclose这样的操作来释放。要在shared_ptr里使用 FILE*,我们要定义一个类来负责释放相应的资源。
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";
}
使用定制删除器的安全性
我们已经看到对基类使用 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();
}
weak_ptr
头文件: "boost/weak_ptr.hpp"
weak_ptr是 shared_ptr的观察员。它不会干扰shared_ptr所共享的所有权。当一个被weak_ptr所观察的 shared_ptr要释放它的资源时,它会把相关的 weak_ptr的指针设为空。这防止了 weak_ptr持有悬空的指针。你为什么会需要 weak_ptr? 许多情况下,你需要旁观或使用一个共享资源,但不接受所有权,如为了防止递归的依赖关系,你就要旁观一个共享资源而不能拥有所有权,或者为了避免悬空指针。可以从一个weak_ptr构造一个shared_ptr,从而取得对共享资源的访问权。
shared_ptr<T> lock() const
返回一个引向weak_ptr所观察的资源的 shared_ptr,如果可以的话。如果没有这样指针(即 weak_ptr引向的是空指针),shared_ptr也将引向空指针。否则,shared_ptr所引向的资源的引用计数将正常地递增。这个函数不会抛出异常。
用法
我们从一个示范weak_ptr的基本用法的例子开始,尤其要看看它是如何不影响引用计数的。这个例子里也包含了 shared_ptr,因为 weak_ptr总是需要和 shared_ptr一起使用的。使用 weak_ptr要包含头文件 "boost/weak_ptr.hpp".
#include
"boost/shared_ptr.hpp"
#include
"boost/weak_ptr.hpp"
#include <iostream>
#include
<cassert>
class A {};
int main() {
boost::weak_ptr<A> w;
assert(w.expired());
{
boost::shared_ptr<A> p(new A());
assert(p.use_count()==1);
w=p;
assert(p.use_count()==w.use_count());
assert(p.use_count()==1);
// 从weak_ptr创建shared_ptr
boost::shared_ptr<A> p2(w);
assert(p2==p);
}
assert(w.expired());
boost::shared_ptr<A>
p3=w.lock();
assert(!p3);
}
两种从weak_ptr创建shared_ptr的惯用法
如你所见,如果你有一个旁观某种资源的 weak_ptr ,你最终还是会想要访问这个资源。为此,weak_ptr 必须被转换为 shared_ptr, 因为 weak_ptr 是不允许访问资源的。有两种方法可以从weak_ptr创建shared_ptr:把 weak_ptr 传递给 shared_ptr 的构造函数,或者调用 weak_ptr 的成员函数lock, 它返回 shared_ptr. 选择哪一个取决于你认为一个空的 weak_ptr 是错误的抑或不是。shared_ptr 构造函数在接受一个空的 weak_ptr 参数时会抛出一个 bad_weak_ptr 类型的异常。因此应该在你认为空的 weak_ptr 是一种错误时使用它。如果使用 weak_ptr 的函数 lock, 它会在weak_ptr为空时返回一个空的 shared_ptr。这在你想测试一个资源是否有效时是正确的,一个空的 weak_ptr 是预期中的。此外,如果使用 lock, 那么使用资源的正确方法应该是初始化并同时测试它,如下:
intrusive_ptr
头文件: "boost/intrusive_ptr.hpp"
intrusive_ptr 是shared_ptr的插入式版本。有时我们必须使用插入式的引用计数智能指针。典型的情况是对于那些已经写好了内部引用计数器的代码,而我们又没有时间去重写它(或者已经不能获得那些代码了)。另一种情况是要求智能指针的大小必须与裸指针大小严格相等,或者shared_ptr的引用计数器分配严重影响了程序的性能(我可以肯定这是非常罕见的情况!)。从功能的观点来看,唯一需要插入式智能指针的情况是,被指类的某个成员函数需要返回this,以便它可以用于另一个智能指针(事实上,也有办法使用非插入式智能指针来解决这个问题,正如我们在本章前面看到的)。intrusive_ptr 不同于其它智能指针,因为它要求你来提供它所要的引用计数器。
当 intrusive_ptr 递增或递减一个非空指针上的引用计数时,它是通过分别调用函数 intrusive_ptr_add_ref 和 intrusive_ptr_release来完成的。这两个函数负责确保引用计数的正确性,并且负责在引用计数降为零时删除指针。因此,你必须为你的类重载这两个函数,正如我们后面将看到的。
要使用 boost::intrusive_ptr, 要包含 "boost/intrusive_ptr.hpp" 并定义两个普通函数 intrusive_ptr_add_ref 和 intrusive_ptr_release. 它们都要接受一个参数,即指向你要使用intrusive_ptr的类型的指针。这两个函数的返回值被忽略。通常的做法是,泛化这两个函数,简单地调用被管理类型的成员函数去完成工作(例如,调用 add_ref 和 release)。如果引用计数降为零,intrusive_ptr_release 应该负责释放资源。以下是你应该如何实现这两个泛型函数的示范:
template
<typename T> void intrusive_ptr_add_ref(T* t) {
t->add_ref();
}
template <typename T> void
intrusive_ptr_release(T* t) {
if (t->release()<=0)
delete t;
}
class
reference_counter {
int ref_count_;
public:
reference_counter() : ref_count_(0) {}
virtual
~reference_counter() {}
void add_ref() {
++ref_count_;
}
int release() {
return
--ref_count_;
}
protected:
reference_counter&
operator=(const reference_counter&) {
// 无操作
return *this;
}
private:
// 禁止复制构造函数
reference_counter(const reference_counter&);
};
把reference_counter的析构函数声明为虚拟的原因是这个类将被公开继承,有可能会使用一个reference_counter指针来delete派生类。我们希望删除操作能够正确地调用派生类的析构函数。实现非常简单:add_ref 递增引用计数,release 递减引用计数并返回它。要使用这个引用计数,要做的就是公共地继承它。以下是一个类 some_ class ,包含一个内部引用计数,并使用 intrusive_ptr。
#include
<iostream>
#include "boost/intrusive_ptr.hpp"
class
some_class : public reference_counter {
public:
some_class()
{
std::cout << "some_class::some_class()\n";
}
some_class(const some_class& other) {
std::cout << "some_class(const some_class& other)\n";
}
~some_class() {
std::cout <<
"some_class::~some_class()\n";
}
};
int
main() {
std::cout << "Before start of scope\n";
{
boost::intrusive_ptr<some_class> p1(new
some_class());
boost::intrusive_ptr<some_class> p2(p1);
}
std::cout << "After end of scope \n";
}
为了显示 intrusive_ptr以及函数 intrusive_ptr_add_ref 和 intrusive_ptr_release 都正确无误,以下是这个程序的运行输出:
Before
start of
scope
some_class::some_class()
some_class::~some_class()
After
end of scope
支持不同的引用计数器
我们前面提过可以为不同的类型支持不同的引用计数。这在集成已有的采用不同引用计数机制的类时是有必要的(例如,第三方的类使用它们自己版本的引用计数器)。又或者对于资源的释放有不同的需求,如调用delete以外的另一个函数。如前所述,对 intrusive_ptr_add_ref 和 intrusive_ptr_release 的调用是非受限的。这意味着在名字查找时要考虑参数(指针的类型)的作用域,从而这些函数应该与它们操作的类型定义在同一个作用域。如果你在全局名字空间里实现 intrusive_ptr_add_ref 和 intrusive_ptr_release 的泛型版本,你就不能在其它名字空间中再创建泛型版本了。例如,如果一个名字空间需要为它的所有类型定义一个特殊的版本,特化版本或重载版本必须提供给每一个类型。否则,全局名字空间中的函数就会引起歧义。因此在全局名字空间中提供泛型版本不是一个好主意,而在其它名字空间中提供则可以。
既然我们已经用基类reference_counter实现了引用计数器,那么在全局名字空间中提供一个接受reference_counter*类型的参数的普通函数应该是一个好主意。这还可以让我们在其它名字空间中提供泛型重载版本而不会引起歧义。例如,考虑my_namespace名字空间中的两个类 another_class 和 derived_class :
namespace
my_namespace {
class another_class : public reference_counter {
public:
void call_before_destruction() const {
std::cout <<
"Yes, I'm ready before
destruction\n";
}
};
class derived_class :
public another_class {};
template <typename T> void
intrusive_ptr_add_ref(T* t) {
t->add_ref();
}
template <typename T> void intrusive_ptr_release(T* t) {
if (t->release()<=0) {
t->call_before_destruction();
delete t;
}
}
}
这里,我们实现了intrusive_ptr_add_ref 和 intrusive_ptr_release的泛型版本。因此我们必须删掉在全局名字空间中的泛型版本,把它们替换为以一个reference_counter指针为参数的非模板版本。或者,我们干脆从全局名字空间中删掉这些函数,也可以避免引起混乱。对于这两个类 my_namespace::another_class 和 my_namespace::derived_class, 将调用这个特殊版本(那个调用了它的参数的成员函数 call_before_destruction 的版本)。其它类型或者在它们定义所在的名字空间中有相应的函数,或者使用全局名字空间中的版本,如果有的话。下面程序示范了这如何工作: