分类:
2010-11-22 15:14:11
namespace boost {
template class shared_ptr {
public:
template explicit shared_ptr(Y* p);
template shared_ptr(Y* p,D d);
~shared_ptr();
shared_ptr(const shared_ptr & r);
template explicit
shared_ptr(const weak_ptr& r);
template explicit shared_ptr(std::auto_ptr& 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;
void swap(shared_ptr& b);
};
template
shared_ptr static_pointer_cast(const shared_ptr& r);
}
#include
#include
template
class CResourceObserver
{
public:
CResourceObserver()
{
cout < }
CResourceObserver(const CResourceObserver& orig)
{
cout < }
operator=(const CResourceObserver& orig)
{
cout < }
virtual ~CResourceObserver(void)
{
cout < }
};
这个类,利用了运行时类型识别及模板,这样发生与资源有关的操作时,都能通过输出恰当的反映出来。shared_ptr的最简单应用#include
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver
{
};
void Fun()
{
shared_ptr sp(new MyClass);
}
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." < Fun();
cout <<"Fun ended." <
return 0;
}
输出如下:
Fun called.
class MyClass Construct.
class MyClass Deconstruct.
Fun ended.
我们只new,并没有显式的delete,但是MyClass很显然也是析构了的。
这里将shared_ptr替换成auto_ptr也是完全可以的,效果也一样。
shared_ptr的与auto_ptr的区别
shared_ptr与auto_ptr的区别在于所有权的控制上。如下例:
例二:
#include
#include
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver
{
public:
MyClass() : CResourceObserver
{
mstr = typeid(MyClass).name();
}
void print()
{
cout <
std::string mstr;
};
typedef shared_ptr
//typedef auto_ptr
void Fun2(spclass_t& asp)
{
spclass_t sp3(asp);
cout <
asp->print();
return;
}
void Fun()
{
spclass_t sp(new MyClass);
cout <
spclass_t sp2(sp);
cout <
Fun2(sp);
cout <
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." <
cout <<"Fun ended." <
return 0;
}
Fun called.
class MyClass Construct.
1
2
3
class MyClass print
2
class MyClass Deconstruct.
Fun ended.
此例中将shared_ptr分配的资源复制了3份(实际是管理权的复制,资源明显没有复制),每一个shared_ptr结束其生命周期时释放一份管理权。每一个都有同等的使用权限。输出的引用计数数量显式了这一切。在这里,可以尝试替换shared_ptr到auto_ptr,这个程序没有办法正确运行。
shared_ptr的引用计数共享所有权
因为没有拷贝构造及operator=的操作,我们可以知道,对象没有被复制,为了证实其使用的都是同一个资源,这里再用一个例子证明一下:
例3:
#include
#include
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver
{
public:
MyClass() : CResourceObserver
{
mstr = typeid(MyClass).name();
}
void set(const char* asz)
{
mstr = asz;
}
void print()
{
cout <
std::string mstr;
};
typedef shared_ptr
void Fun2(spclass_t& asp)
{
spclass_t sp3(asp);
sp3->set("New Name");
return;
}
void Fun()
{
spclass_t sp(new MyClass);
spclass_t sp2(sp);
Fun2(sp);
sp->print();
sp2->print();
}
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." <
cout <<"Fun ended." <
return 0;
}
输出:
Fun called.
class MyClass Construct.
New Name print
New Name print
class MyClass Deconstruct.
Fun ended.
shared_ptr与标准库容器
在标准库容器中存入普通指针来实现某个动态绑定的实现是很普遍的事情,但是实际上每次都得记住资源的释放,这也是BoundsChecker误报的最多的地方。
例4:
#include
#include
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver
{
public:
MyClass() : CResourceObserver
{
mstr = typeid(MyClass).name();
}
void set(const char* asz)
{
mstr = asz;
}
void print()
{
cout <
std::string mstr;
};
typedef shared_ptr
typedef vector< shared_ptr
void Fun()
{
spclassVec_t spVec;
spclass_t sp(new MyClass);
spclass_t sp2(sp);
cout <
spVec.push_back(sp);
spVec.push_back(sp2);
cout <
sp2->set("New Name");
sp->print();
sp2->print();
spVec.pop_back();
cout <
sp->print();
sp2->print();
}
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." <
cout <<"Fun ended." <
return 0;
}
输出:
Fun called.
class MyClass Construct.
2
2
4
4
New Name print
New Name print
3
3
New Name print
New Name print
class MyClass Deconstruct.
Fun ended.
当在标准库容器中保存的是shared_ptr时,几乎就可以不考虑资源释放的问题了,该释放的时候自然就释放了,当一个资源从一个容器辗转传递几个地方的时候,常常会搞不清楚在哪个地方统一释放合适,用了shared_ptr后,这个问题就可以不管了,每次的容器Item的添加增加计数,容器Item的减少就减少计数,恰当的时候,就释放了。方便不可言喻。
3.智能指针的高级应用:
已经说的够多了,再说下去几乎就要脱离讲解智能指针与异常的本意了,一些很有用的应用就留待大家自己去查看资料吧。
1.定制删除器,shared_ptr允许通过定制删除器的方式将其用于其它资源的管理,几乎只要是通过分配,释放形式分配的资源都可以纳入shared_ptr的管理范围,比如文件的打开关闭,目录的打开关闭等自然不在话下,甚至连临界区,互斥对象这样的复杂对象,一样可以纳入shared_ptr的管理。
2.从this创建shared_ptr。
以上两点内容在《Beyond the C++ Standard Library: An Introduction to Boost》智能指针的专题中讲解了一些,但是稍感不够详细,但是我也没有看到更为详细的资料,聊胜于无吧。
3.Pimpl:
《Beyond the C++ Standard Library: An Introduction to Boost》中将智能指针的时候有提及,在《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》 第43 条Pimpl judiciously中对其使用的好处,坏处,方式等都有较为详细的讲解,大家可以去参考一下,我就不在此继续献丑了。
以下是《Beyond the C++ Standard Library: An Introduction to Boost》对shared_ptr使用的建议:
1.当有多个使用者使用同一个对象,而没有一个明显的拥有者时
2.当要把指针存入标准库容器时
3.当要传送对象到库或从库获取对象,而没有明确的所有权时
4.当管理一些需要特殊清除方式的资源时
三、 智能指针与C++异常
因为考虑到大家可能对智能指针不够熟悉,所以讲到这里的时候对智能指针进行了较多的讲解,几乎就脱离主题了,在这里开始进入正题。
一开始我就讲了智能指针对于C++的异常机制非常重要,到底重要在哪里呢?这里看两个C++异常的例子,一个使用了没有使用智能指针,一个使用了智能指针。
void Fun()
{
MyClass* lp1 = NULL;
MyClass* lp2 = NULL;
MyClass* lp3 = NULL;
lp1 = new MyClass;
try
{
lp2 = new MyClass;
}
catch(bad_alloc)
{
delete lp1;
}
try
{
lp3 = new MyClass;
}
catch(bad_alloc)
{
delete lp1;
delete lp2;
}
// Do Something.....
delete lp1;
delete lp2;
delete lp3;
}
void Fun2()
{
try
{
spclass_t sp1(new MyClass);
spclass_t sp2(new MyClass);
spclass_t sp3(new MyClass);
}
catch(bad_alloc)
{
// No need to delete anything
}
// Do Something
// No need to delete anything
}
区别,好处,明眼人不用看第二眼。这里为了简单,用内存的作为示例,虽然现在内存分配的情况很少见了,但是其他资源原理上是一样的,个人经验最深的地方实在Python的C API使用上,哪怕一个简单的C++,Python函数相互调用,都会有N个PyObject*出来,一个一个又一个,直到头昏脑胀,使用了智能指针后,简化的不止一半代码。
其实从本质上来讲,异常属于增加了程序从函数退出的路径,而C++原来的内存管理机制要求每个分支都需要手动的释放每个分配了的资源,这是本质的复杂度,在用于普通return返回的时候,还有一些hack技巧,见《do...while(0)的妙用》,但是异常发生的时候,能够依赖的就只有手动和智能指针两种选择了。
在没有智能指针的光使用异常的时候,甚至会抱怨因为异常增加了函数的出口,导致代码的膨胀,说智能指针是C++异常处理的绝佳搭档就在于其弥补的此缺点。
另外,其实很多语言还有个finally的异常语法,JAVA,Python都有,SEH也有,其与使用了智能指针的C++异常比较在刘未鹏关于异常处理的文章《错误处理(Error-Handling):为何、何时、如何(rev#2)》中也有详细描述,我就不在此多费口舌了,将来讲SEH的时候自然还会碰到。个人感觉是,有也不错。毕竟,不是人人都有机会在每个地方都用上智能指针。