int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
这样使用是不对的,sp1和sp2两者互相不知道对方,所以当离开作用域的时候,会释放两遍。 同样的道理:如果一个成员函数返回this的shared_ptr,也会出现上面的问题。成员函数dangerous是不会了解外面的sp1已经拥有了shared_ptr的资源。
struct S
{
shared_ptr<S> dangerous()
{
return shared_ptr<S>(this); // don't do this!
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->dangerous();
return 0;
}
正确的编写dangerous的姿势是如下:
struct S : enable_shared_from_this<S>
{
shared_ptr<S> not_dangerous()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->not_dangerous();
return 0;
}
class Example : public boost::enable_shared_from_this < Example >{
public:
int x;
Example() :x(233){}
Example(int _x) :x(_x){}
};
int main(){
for (;;){
boost::shared_ptr<Example> sp = boost::make_shared<Example>(666);
assert(sp->x == 666);
boost::shared_ptr<Example> p = sp->shared_from_this();
assert(p.use_count() == 2);
assert(p->x == 666);
}
}
但是要注意的是not_dangerous正确的调用方式是,shared_ptr包裹新创建的对象后创建
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
std::shared_ptr<int> p(new int); // or '=shared_ptr<int>(new int)' if you insist
auto p = std::make_shared<int>(); // or 'std::shared_ptr<int> p' if you insist
两者的区别和联系:
- 第一种要分配内存两次,一次给管理的对象,另一个是给引用计数,
- make_shared则是只有一次内存。
int* p = new int(233);
boost::shared_ptr<int> sp(p);
//然后就不能再delete p了, 否则重复释放
boost::shared_ptr<int> sp;
assert(!sp);
boost::shared_ptr<int> sp;
assert(!sp);
int* p = new int(233);
sp.reset(p);
assert(sp);
assert(233 == *sp);
void f(shared_ptr<int>, int);
int g();
void ok(){
shared_ptr<int> p( new int(2) );
f( p, g() );
}
void bad(){
f( shared_ptr<int>( new int(2) ), g() );
}
class Humen{};
class Male:public Humen{
boost::shared_ptr<Humen> _wife;
};
class Female:public Humen{
boost::shared_ptr<Humen> _husband;
};
class SingleDog:public Humen{
};
但我真需要一个mate怎么办? boost还有weak_ptr.
wear_ptr是一个弱化的shared_ptr, 它的存在不会阻碍析构, 而且可以从一个有效的wear_ptr中获取对应的shared_ptr。weak_ptr相当于一个旁观者, 它并不提供完整的指针操作, 如果你要使用其管理的对象, 必须取其shared_ptr.
将以上代码换成如下便可破解循环引用:
class Human{
public:
Human(){};
boost::weak_ptr<Human> _mate;
};
上面已经提到了weak_ptr, 可以看到, weak_ptr是为了配合shared_ptr而引入的一种智能指针, 它更像shared_ptr的一个助手而不是智能指针。
假设有 Stock 类,代表一只股票的价格。每一只股票有一个唯一的字符串标识,比如 Google 的 key 是 "NASDAQ:GOOG",IBM 是 "NYSE:IBM"。Stock 对象是个主动对象,它能不断获取新价格。为了节省系统资源,同一个程序里边每一只出现的股票只有一个 Stock 对象,如果多处用到同一只股票,那么 Stock 对象应该被共享。如果某一只股票没有再在任何地方用到,其对应的 Stock 对象应该析构,以释放资源,这隐含了“引用计数”。
// version 1: questionable code
class StockFactory : boost::noncopyable
{
public:
shared_ptr<Stock> get(const string& key);
private:
mutable MutexLock mutex_;
std::map<string, shared_ptr<Stock> > stocks_;
};
get() 的逻辑很简单,如果在 stocks_ 里找到了 key,就返回 stocks_[key];否则新建一个 Stock,并存入 stocks_[key]。细心的读者或许已经发现这里有一个问题,Stock 对象永远不会被销毁,因为map 里存的是 shared_ptr,始终有“铁丝”绑着。
// // version 2: 数据成员修改为 std::map<string, weak_ptr<Stock> > stocks_;
shared_ptr<Stock> StockFactory::get(const string& key)
{
shared_ptr<Stock> pStock;
MutexLockGuard lock(mutex_);
weak_ptr<Stock>& wkStock = stocks_[key]; // 如果 key 不存在,会默认构造一个
pStock = wkStock.lock(); // 尝试把“棉线”提升为“铁丝”
if (!pStock) {
pStock.reset(new Stock(key));
wkStock = pStock; // 这里更新了 stocks_[key],注意 wkStock 是个引用
}
return pStock;
}
这么做固然 Stock 对象是销毁了,但是程序却出现了轻微的内存泄漏,为什么? 因为 stocks_ 的大小只增不减,stocks_.size() 是曾经存活过的 Stock 对象的总数,即便活的 Stock 对象数目降为 0。或许有人认为这不算泄漏,因为内存并不是彻底遗失不能访问了,而是被某个标准库容器占用了。我认为这也算内存泄漏,毕竟 是“战场”没有打扫干净。
其实,考虑到世界上的股票数目是有限的,这个内存不会一直泄漏下去,大不了把每只股票的对象都创建一遍,估计泄漏的内存也只有几兆字节。如果这是一个其他类型的对象池,对象的 key 的集合不是封闭的,内存就会一直泄漏下去。
解决的办法是,利用 shared_ptr 的定制析构功能。shared_ptr 的构造函数可以有一个额外的模板类型参数,传入一个函数指针或仿函数 d,在析构对象时执行d(ptr),其中 ptr 是 shared_ptr 保存的对象指针。shared_ptr 这么设计并不是多余的,因为反正要在创建对象时捕获释放动作,始终需要一个 bridge。
// version 3
class StockFactory : boost::noncopyable
{
// 在 get() 中,将 pStock.reset(new Stock(key)); 改为:
// pStock.reset(new Stock(key),
// boost::bind(&StockFactory::deleteStock, this, _1)); // ***
private:
void deleteStock(Stock* stock) {
if (stock) {
MutexLockGuard lock(mutex_);
stocks_.erase(stock->key());
}
delete stock; // sorry, I lied
}
// assuming StockFactory lives longer than all Stock's ...
// ...
警惕的读者可能已经发现问题,那就是我们把一个原始的 StockFactory this 指针保存在了 boost::function 里(*** 处),这会有线程安全问题。如果这个 Stock-Factory 先于 Stock 对象析构,那么会 core dump。
StockFactory::get() 把原始指针 this 保存到了 boost::function 中(*** 处),如果 StockFactory 的 生 命 期 比 Stock 短, 那 么 Stock 析 构 时 去 回 调 StockFactory::deleteStock 就会 core dump。似乎我们应该祭出惯用的 shared_ptr 大法来解决对象生命期问题,但是 StockFactory::get() 本身是个成员函数,如何获得一个指向当前对象的 shared_ptr 对象呢?
为了使用 shared_from_this(),StockFactory 不能是 stack object,必须是 heap object 且由 shared_ptr 管理其生命期,即:
shared_ptr<StockFactory> stockFactory(new StockFactory);
万事俱备,可以让 this 摇身一变,化为 shared_ptr 了。
// version 4
shared_ptr<Stock> StockFactory::get(const string& key)
{
// change
// pStock.reset(new Stock(key),
// boost::bind(&StockFactory::deleteStock, this, _1));
// to
pStock.reset(new Stock(key),
boost::bind(&StockFactory::deleteStock, shared_from_this(), _1));
// ...
这样一来,boost::function 里保存了一份 shared_ptr,可以保证调用 StockFactory::deleteStock 的时候那个 StockFactory 对象还活着。 注意一点,shared_from_this() 不能在构造函数里调用,因为在构造 StockFactory 的时候,它还没有被交给 shared_ptr 接管。
最后一个问题,StockFactory 的生命期似乎被意外延长了。
把 shared_ptr 绑(boost::bind)到 boost:function 里,那么回调的时候 StockFactory 对象始终存在,是安全的。这同时也延长了对象的生命期,使之不短于绑得的 boost:function 对象。
有时候我们需要“如果对象还活着,就调用它的成员函数,否则忽略之”的语意,就像 Observable::notifyObservers() 那样,我称之为“弱回调”。这也是可以实现的,利用 weak_ptr,我们可以把 weak_ptr 绑到 boost::function 里,这样对象的生命期就不会被延长。然后在回调的时候先尝试提升为 shared_ptr,如果提升成功,说明接受回调的对象还健在,那么就执行回调;如果提升失败,就不必劳神了。
class StockFactory : public boost::enable_shared_from_this<StockFactory> {
public:
shared_ptr<Stock> get(const string& key) {
shared_ptr<Stock> pStock;
MutexLockGuard lock(mutex_);
weak_ptr<Stock>& wkStock = stocks_[key]; // 注意 wkStock 是引用
pStock = wkStock.lock();
if (!pStock) {
pStock.reset(new Stock(key),
boost::bind(&StockFactory::weakDeleteCallback,
boost::weak_ptr<StockFactory>(shared_from_this()), _1));
// 上面必须强制把 shared_from_this() 转型为 weak_ptr,才不会延长生命期,
// 因为 boost::bind 拷贝的是实参类型,不是形参类型
wkStock = pStock;
}
return pStock;
}
private:
static void weakDeleteCallback(const boost::weak_ptr<StockFactory>& wkFactory, Stock* stock) {
shared_ptr<StockFactory> factory(wkFactory.lock()); // 尝试提升
if (factory) // 如果 factory 还在,那就清理 stocks_
{
factory->removeStock(stock);
}
delete stock; // sorry, I lied
}
};
- 一个 shared_ptr 对象实体可被多个线程同时读取;
- 两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;
- 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。
请注意,以上是 shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。 良好的使用shared_ptr的方式
void read()
{
shared_ptr<Foo> localPtr;
{
MutexLockGuard lock(mutex);
localPtr = globalPtr; // read globalPtr
}
// use localPtr since here,读写 localPtr 也无须加锁
doit(localPtr);
}
写也要加锁:
void write()
{
shared_ptr<Foo> newPtr(new Foo); // 注意,对象的创建在临界区之外
{
MutexLockGuard lock(mutex);
globalPtr = newPtr; // write to globalPtr
}
// use newPtr since here,读写 newPtr 无须加锁
doit(newPtr);
}
class Request {
std::shared_ptr<void> pData;
public:
//Called by clients to set user-data
void setUserData(const std::shared_ptr<void>& p) {
pData = p;
}
};
class Response {
std::shared_ptr<void> pData; // Copied from Request
public:
//Called by clients
template<typename T>
auto getUserData() {
return std::static_pointer_cast<T>(pData);
}
};
//Create a request and attach user data
Request r;
r.setUserData(std::make_shared<Context>(/*..*/)); // Set a context
// Send request asynchrounously.
// Response handler callback
void onResponse(Response* pr) {
auto pc = pr->getUserData<Context>();
// Process response using context....
// No explicit context delete here
}