Please indicate the source: http://blog.csdn.net/gaoxiangnumber1
Welcome to my github: https://github.com/gaoxiangnumber1
1.8 应用到 Observer 上
?既然通过 weak_ptr 能探查对象的生死,那么 Observer 模式的竞态条件就很容易解决,只要让Observable 保存 weak_ptr 即可:
?前文代码 (3) 处(p. 10 的 L17)的竞态条件已经弥补了。
?思考:如果把 L48 改为 vector
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);
}
?read()和write()在临界区之外都没有再访问globalPtr,而是用了一个指向同一Foo对象的栈上shared_ptr local copy。只要有这样的local copy 存在,shared_ptr 作为函数参数传递时不必复制,用 reference to const 作为参数类型即可。
?上面的new Foo是在临界区之外执行的,这种写法比在临界区内写 globalPtr.reset(new Foo) 要好,因为缩短了临界区长度。
?如果要销毁对象,虽然我们可以在临界区内执行globalPtr.reset(),但是这样往往会让对象析构发生在临界区以内,增加了临界区的长度。改进办法是定义一个localPtr,用它在临界区内与globalPtr交换(swap()),这样能保证把对象的销毁推迟到临界区之外。练习:在write()函数中,globalPtr = newPtr; 这一句有可能会在临界区内销毁原来globalPtr指向的Foo对象,设法将销毁行为移出临界区。
1.10 shared_ptr 技术与陷阱
意外延长对象的生命期
?shared_ptr是强引用,只要有一个指向x对象的shared_ptr存在,该对象就不会析构。而shared_ptr允许拷贝构造和赋值,如果遗留了一个拷贝,那么对象就永存了。例如如果把p.16中L48 observers_的类型改为 vector
Observer::~Observer()
{
subject_->unregister(this);
}
void Observable::unregister(Observer* x)
{
std::vector<Observer*>::iterator it =
std::find(observers_.begin(), observers_.end(), x);
if (it != observers_.end())
{
std::swap(*it, observers_.back());
observers_.pop_back();
}
}
?另一个出错的可能是boost::bind,因为 boost::bind 会把实参拷贝一份,如果参数是个 shared_ptr,那么对象的生命期就不会短于 boost::function 对象:
class Foo
{
void doit();
};
shared_ptr<Foo> pFoo(new Foo);
boost::function<void()> func = boost::bind(&Foo::doit, pFoo); // long life foo
?这里 func 对象持有了 shared_ptr 的一份拷贝,有可能会在不经意间延长倒数第二行创建的 Foo 对象的生命期。
函数参数
?因为要修改引用计数,而且拷贝的时候通常要加锁,shared_ptr的拷贝开销比拷贝原始指针要高,但是需要拷贝的时候并不多。多数情况下它可以以const reference方式传递,一个线程只需要在最外层函数有一个实体对象,之后都可以用const reference来使用这个shared_ptr。例如有几个函数都要用到Foo对象:
void save(const shared_ptr<Foo>& pFoo); // pass by const reference
void validateAccount(const Foo& foo);
bool validate(const shared_ptr<Foo>& pFoo) // pass by const reference
{
validateAccount(*pFoo);
// ...
}
?在通常情况下,我们可以传常引用(pass by const reference):
void onMessage(const string& msg)
{
//只要在最外层持有一个实体,无安全问题
shared_ptr<Foo> pFoo(new Foo(msg));
if (validate(pFoo)) // 没有拷贝 pFoo
{
save(pFoo); // 没有拷贝 pFoo
}
}
?遵照这个规则,基本上不会遇到反复拷贝 shared_ptr 导致的性能问题。由于pFoo是栈上对象,不可能被别的线程看到,那么读取始终是线程安全的。
析构动作在创建时被捕获
?这是一个非常有用的特性,这意味着:
1.虚析构不再是必需的。
2.shared_ptr 可以持有任何对象,而且能安全地释放。
3.shared_ptr对象可以安全地跨越模块边界,比如从 DLL 里返回,而不会造成从模块 A 分配的内存在模块 B 里被释放这种错误。
4.二进制兼容性,即便 Foo 对象的大小变了,那么旧的客户代码仍然可以使用新的动态库,而无须重新编译。前提是 Foo 的头文件中不出现访问对象的成员的inline 函数,并且 Foo 对象由动态库中的 Factory 构造,返回其 shared_ptr。
5.析构动作可以定制。
?最后特性的实现比较巧妙,因为shared_ptr只有一个模板参数,而“析构行为”可以是函数指针、仿函数(functor)或者其他什么东西。这是泛型编程和面向对象编程的一次完美结合。有兴趣的读者可以参考 Scott Meyers 的文章http://www.artima.com/cppsource/top_cpp_aha_moments.html。这个技术在后面的对象池中会用到。
析构所在的线程
?对象的析构是同步的,当最后一个指向 x 的 shared_ptr 离开其作用域的时候, x 会同时在同一个线程析构。这个线程不一定是对象诞生的线程。
?这个特性是把双刃剑:
1.弊:如果最后一个shared_ptr引发的析构发生在关键线程,并且对象的析构比较耗时,那么可能会拖慢关键线程的速度;
2.利:可以用一个单独的线程来专门析构,通过一个BlockingQueue
// 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];
—没找到key,新建一个Stock,并存入stocks_[key]。
?问题:Stock对象永远不会被销毁,因为map里存的是shared_ptr。应该存weak_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 保存的对象指针。这么设计不是多余的,因为要在创建对象时捕获释放动作,始终需要一个 bridge。
template<class Y, class D>
shared_ptr::shared_ptr(Y* p, D d);
template<class Y, class D>
void shared_ptr::reset(Y* p, D d);
Y的类型可能与T不同,只要 Y* 能隐式转换为 T*,就是合法的,。
?我们可以利用这一点,在析构 Stock 对象的同时清理 stocks_。
// 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 ...
// ...
}
?这里向pStock.reset()传递了第二个参数,一个boost::function,让它在析构Stock* stock时调用本StockFactory对象的deleteStock成员函数。
?问题:我们把一个原始的 StockFactory this 指针保存在了 boost::function 里,这有线程安全问题。如果这个 StockFactory 先于 Stock 对象析构,那么会 core dump。类比Observer在析构函数里去调用 Observable::unregister(),而那时 Observable 对象可能已经不存在了。这要用到 § 1.11.2 介绍的弱回调技术解决。
1.11.1 enable_shared_from_this
?StockFactory::get()把原始指针this保存到了boost::function中,如果StockFactory的生命期比Stock短, 那么Stock析构时去回调StockFactory::deleteStock就会 core dump。我们应该用shared_ptr来解决对象生命期问题,因为StockFactory::get() 本身是个成员函数,所以要获得一个指向当前对象的shared_ptr对象。
?用enable_shared_from_this。这是一个以其派生类为模板类型实参的基类模板14 http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern,继承它,this指针就能变身为shared_ptr。
class StockFactory : public boost::enable_shared_from_this<StockFactory>,
boost::noncopyable
{ /* ... */ };
?为了使用shared_from_this(),StockFactory不能是stack object,必须是heap object且由shared_ptr管理其生命期,即:
shared_ptr 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()不能在构造函数里调用,因为在构造的时候,对象还没有被交给shared_ptr接管。
?最后一个问题,StockFactory的生命期被意外延长了。
1.11.2 弱回调
?把shared_ptr绑(boost::bind)到boost::function里,那么回调的时候StockFactory对象始终存在,是安全的。这同时延长了对象的生命期,使之不短于绑得的boost::function对象。
?有时需要“如果对象还活着,就调用它的成员函数,否则忽略之”的语意,就像Observable::notifyObservers()那样,这称为“弱回调”。我们可以把weak_ptr绑到 boost::function里,这样对象的生命期就不会被延长。然后在回调的时候先尝试提升为shared_ptr,如果提升成功,说明接受回调的对象还存在,那么就执行回调;如果提升失败,不执行操作。
?使用这一技术的完整 StockFactory 代码如下:
class StockFactory : public boost::enable_shared_from_this<StockFactory>,
boost::noncopyable
{
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
}
void removeStock(Stock* stock)
{
if (stock)
{
MutexLockGuard lock(mutex_);
stocks_.erase(stock->key());
}
}
private:
mutable MutexLock mutex_;
std::map<string, weak_ptr<Stock> > stocks_;
};
两个简单的测试:
void testLongLifeFactory()
{
shared_ptr<StockFactory> factory(new StockFactory);
{
shared_ptr<Stock> stock = factory->get("NYSE:IBM");
shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
assert(stock == stock2);
// stock destructs here
}
// factory destructs here
}
void testShortLifeFactory()
{
shared_ptr<Stock> stock;
{
shared_ptr<StockFactory> factory(new StockFactory);
stock = factory->get("NYSE:IBM");
shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
assert(stock == stock2);
// factory destructs here
}
// stock destructs here
}
Code
#include <map>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include "mutex.h"
#include <stdio.h>
using std::string;
class Stock
{
private:
string name_;
public:
Stock(const string &name) : name_(name)
{
printf("Constructor:\tStock[%p] %s\n", this, name_.c_str());
}
~Stock()
{
printf("Destructor:\t~Stock[%p] %s\n", this, name_.c_str());
}
const string &key() const
{
return name_;
}
};
class StockFactory : public boost::enable_shared_from_this<StockFactory>
{
private:
mutable MutexLock mutex_;
std::map<string, boost::weak_ptr<Stock> > stocks_;
static void deleteStock(const boost::weak_ptr<StockFactory> &wkFactory, Stock *stock)
{
printf("weakDeleteStock[%p]\n", stock);
boost::shared_ptr<StockFactory> factory(wkFactory.lock());
if(factory)
{
factory->removeStock(stock);
}
else
{
printf("factory died.\n");
}
delete stock;
}
void removeStock(Stock *stock)
{
if(stock)
{
MutexLockGuard lock(mutex_);
stocks_.erase(stock->key());
}
}
public:
StockFactory()
{
printf("Constructor:\tStockFactory[%p]\n", this);
}
~StockFactory()
{
printf("Destructor:\t~StockFactory[%p]\n", this);
}
boost::shared_ptr<Stock> get(const string &key)
{
MutexLockGuard lock(mutex_);
boost::weak_ptr<Stock> &wkStock = stocks_[key];
boost::shared_ptr<Stock> pStock = wkStock.lock();
if(!pStock)
{
pStock.reset(new Stock(key),
boost::bind(&StockFactory::deleteStock,
boost::weak_ptr<StockFactory>(shared_from_this()),
_1));
wkStock = pStock;
}
return pStock;
}
};
void testLongLifeFactory()
{
boost::shared_ptr<StockFactory> factory(new StockFactory);
{
boost::shared_ptr<Stock> stock = factory->get("NYSE:IBM");
boost::shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
if(stock == stock2)
{
printf("Good:\tstock == stock2\n");
}
// stock destructs here
}
// factory destructs here
}
void testShortLifeFactory()
{
boost::shared_ptr<Stock> stock;
{
boost::shared_ptr<StockFactory> factory(new StockFactory);
stock = factory->get("NYSE:IBM");
boost::shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
if(stock == stock2)
{
printf("Good:\tstock == stock2\n");
}
// factory destructs here
}
// stock destructs here
}
int main()
{
boost::shared_ptr<StockFactory> factory(new StockFactory);
printf("\nAAAAAAAAAAAAAAAAAAAAAA\n");
{
boost::shared_ptr<Stock> stock = factory->get("stock");
}
printf("BBBBBBBBBBBBBBBBBBBBBBBB\n");
printf("\nCCCCCCCCCCCCCCCCCCCCCCCC\n");
testLongLifeFactory();
printf("DDDDDDDDDDDDDDDDDDDDDD\n");
printf("\nEEEEEEEEEEEEEEEEEEEEEEEEEE\n");
testShortLifeFactory();
printf("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n");
return 0;
}
?这下完美了,无论Stock和StockFactory谁先挂掉都不会影响程序的正确运行。
?通常Factory对象是个singleton,在程序正常运行期间不会销毁,这里只是为了展示弱回调技术15(通用的弱回调封装见recipes/thread/WeakCallback.h,用到了 C++11的variadic template和rvalue reference),这个技术在事件通知中非常有用。
?本节的StockFactory只有针对单个Stock对象的操作,如果程序需要遍历整个stocks_,稍不注意就会造成死锁或数据损坏(§ 2.1) ,请参考 § 2.8 的解决办法。
Please indicate the source: http://blog.csdn.net/gaoxiangnumber1
Welcome to my github: https://github.com/gaoxiangnumber1