《Linux多线程服务端编程》的1.11节以对象池为例子讲解了shared_ptr技术与陷阱。
假设有 Stock 类,代表一只股票的价格。每一只股票有一个惟一的字符串标识,比如 Google 的 key 是 "NASDAQ:GOOG",IBM 是 "NYSE:IBM"。Stock 对象是个主动对象,它能不断获取新价格。为了节省系统资源,同一个程序里边每一只出现的股票只有一个 Stock 对象,如果多处用到同一只股票,那么 Stock 对象应该被共享。如果某一只股票没有再在任何地方用到,其对应的 Stock 对象应该析构,以释放资源,这隐含了“引用计数”。
version1的代码如下:
class StockFactory : boost::noncopyable
{ // questionable code
public:
shared_ptr<Stock> get(const string& key);
private:
std::map<string, shared_ptr<Stock> > stocks_;
mutable Mutex mutex_;
};
version1存在的问题:stocks_里存的是shared_ptr,始终有“铁丝”绑着。
version1的解决办法: 改成weak_ptr,如下的version2:
class StockFactory : boost::noncopyable
{
public:
shared_ptr<Stock> get(const string& key)
{
shared_ptr<Stock> pStock;
MutexLock 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;
}
private:
std::map<string, weak_ptr<Stock> > stocks_;
mutable Mutex mutex_;
};
version2存在的问题: stocks_ 的大小只增不减,stocks_.size() 是曾经存活过的 Stock 对象的总数,即便活的 Stock 对象数目降为 0,出现了轻微的内存泄漏。
version2的解决办法:利用 shared_ptr 的定制析构功能。shared_ptr 的构造函数可以有一个额外的模板类型参数,传入一个函数指针或仿函数 d,在析构对象时执行 d(p),进一步改成version3:
class StockFactory : boost::noncopyable
{
public:
shared_ptr<Stock> get(const string& key)
{
shared_ptr<Stock> pStock;
MutexLock lock(mutex_);
weak_ptr<Stock>& wkStock = stocks_[key]; // 如果 key 不存在,会默认构造一个
pStock = wkStock.lock(); // 尝试把棉线提升为铁丝
if (!pStock) {
pStock.reset(new Stock(key),boost::bind(&StockFactory::deleteStock, this, _1));
wkStock = pStock; // 这里更新了 stocks_[key],注意 wkStock 是个引用
}
return pStock;
}
private:
void deleteStock(Stock* stock)
{
if (stock) {
MutexLock lock(mutex_);
stocks_.erase(stock->key());
}
delete stock; // sorry, I lied
}
version3存在的问题:StockFactory::get() 把原始指针 this 保存到了 boost::function 中 (6),如果 StockFactory 的生命期比 Stock 短,那么 Stock 析构时去回调 StockFactory::deleteStock 就会 core dump。
version3的解决办法:用 enable_shared_from_this。这是一个以其派生类为模版类型实参的基类模版,继承它,this 就能变身为 shared_ptr。修改成如下的 version4:
class StockFactory : public boost::enable_shared_from_this<StockFactory>,
boost::noncopyable
{
public:
shared_ptr<Stock> get(const string& key)
{
shared_ptr<Stock> pStock;
MutexLock lock(mutex_);
weak_ptr<Stock>& wkStock = stocks_[key]; // 如果 key 不存在,会默认构造一个
pStock = wkStock.lock(); // 尝试把棉线提升为铁丝
if (!pStock) {
pStock.reset(new Stock(key),boost::bind(&StockFactory::deleteStock, shared_from_this, _1));
wkStock = pStock; // 这里更新了 stocks_[key],注意 wkStock 是个引用
}
return pStock;
}
}
version4存在的问题:StockFactory 的生命期似乎被意外延长了。
version4的解决办法:利用 weak_ptr,我们可以把 weak_ptr 绑到 boost::function 里,这样对象的生命期就不会被延长,然后在回调的时候先尝试提升为 shared_ptr,如果提升成功,说明接受回调的对象还健在,那么就执行回调;如果提升失败,就不必劳神了。
最终版的代码:
class StockFactory : public boost::enable_shared_from_this<StockFactory>,
boost::noncopyable
{
public:
shared_ptr<Stock> get(const string& key)
{
shared_ptr<Stock> pStock;
MutexLock lock(mutex_);
weak_ptr<Stock>& wkStock = stocks_[key];
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,才不会延长生命期
wkStock = pStock;
}
return pStock;
}
private:
static void weakDeleteCallback(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
}
void removeStock(Stock* stock)
{
if (stock) {
MutexLock lock(mutex_);
stocks_.erase(stock->key());
}
}
private:
std::map<string, weak_ptr<Stock> > stocks_;
mutable Mutex mutex_;
};