奇技淫巧C++之方法代理

wingfiring 2007-03-29 04:17:14
自我感觉很有趣的技术,共享^_^
http://blog.csdn.net/wingfiring/archive/2007/03/28/1543811.aspx

如果你有编写多线程程序的经历,遇到过需要共享对象的情况吗?比如,你想在两个线程中操作同一个容器,你会怎么做呢?是在每个地方都小心翼翼地加锁,还是封装一个来用?两种方法我都用过,但我比较青睐封装一个的办法,例如,封装一个带锁的队列,用于线程间的通信.
  让我们先看看直接操作锁和对象的代码:
//declare:
std::Container cont;
LockType contLock;
...
//using:
contLock.lock();
cont.XXX();
contLock.unlock();
  嗯,using下面的加锁和解锁的方法太老土,而且最关键的,还不安全,让我们稍加改善:
ScopedLock guard(contLock); //在ScopedLock的构造函数中lock,在析构函数中unlock
cont.XXX();
看上去稍好了一点,然而有两个小的缺点。前面提到共享对象,第一个缺点就是我们需要共享两个对象,容器和锁,这使得管理和传递共享对象都变得麻烦起来。另一个缺点是,加锁的动作需要小心谨慎,千万别忘了。可惜,即使忘了,我们也不会从编译器这里得到任何帮助。这两个不便,都会促使我们考虑是不是把对象和锁封装起来更好?很多时候,我们确实是这么做的,看一个deque的例子(省略std).
template<typename T, typename Alloc= allocator<T> >
class shared_deque{
dequeT, Alloc> m_cont;
LockType m_lock;
...
void push_back(const_reference val){
ScopedLock guard(m_lock);
m_cont.push_back(val);
}
....
};
呼,终于好了,我们现在有了一个好用的shared_queue了。只是,类似那个push_back的东西,重复了几十遍,很无聊的,还有必要对 list也来一遍吗?算了吧!万幸,没有用basic_string,那家伙可有100多个成员函数。有没有办法简化一下工作呢?重复的东西总是应该交给计算机来做是不是?还好,C++正好能帮助我们实现这一目标,这个手法在MCD中被寥寥数语带过,就是那神奇的operator->().
让我们回顾一下operator->的用法:用于对象指针,提取对象成员,函数或对象.许多smart pointer地实现都重载了这个运算符,从而可以模拟指针的语法.一般的重载形式是这样的:
cv1 T* operator->() cv2
这里返回的是T*类型,如果返回的不是指针类型呢?C++标准对此有特别规定,会继续调用返回值的operator->()方法,直到最终解析出一个指针类型.假定有下面的operator->展开过程:
object a-->b-->T pointer
(请注意一下a,b,T对象的生命期,确定我们是安全地在使用这些对象.)a对象的operator->返回临时对象b,b对象的operator ->返回最终类型T*.那么,b对象必须在其T* operator->()调用之前创建好,而在T::XXX()方法调用之后被销毁,因为b是临时对象.嗯,好了,有点方向了:在b的构造函数中加锁,在析构中解锁,就可以在调用T::XXX()方法时自动实现加锁可解锁了.
  拓展一下思维,我们必须局限于锁和容器吗?不必.这个手法的本质效果是什么?就是在调用一个方法之前,插入一些操作,调用之后再插入一些操作(稍显遗憾的是,我们无法知道被调用的到底是什么方法).但是,这也够我们做许多事情了.实现如下:
#include
template
<
typename Pointer,
typename Monitor,
typename ScopedType = typename Monitor::scope_type
>
class call_proxy
{
public:
typedef call_proxy self_type;
typedef Pointer pointer_type;
typedef Monitor monitor_type;
typedef ScopedType scoped_type;

typedef typename boost::call_traits<pointer_type>::param_type param_type;
typedef monitor_type& monitor_reference;

private:
struct proxy{
proxy(self_type* host) : m_host(host), m_s(host->m_monitor){};
pointer_type operator->(){
return m_host->m_obj;
}
private:
self_type* m_host;
scoped_type m_s;
proxy(const proxy&);
proxy& operator=(const proxy&)
};
friend struct proxy;
public:
call_proxy(param_type p, monitor_reference m) : m_obj(p), m_monitor(m){ assert(p);}

proxy operator->() {
return this;
}
private:
pointer_type m_obj;
boost::reference_wrapper<monitor_type> m_monitor;
};
为了可以和smart_pointer合作,call_proxy需要的第一个模版参数是被代理对象的指针类型,而不是自己产生指针类型,这就允许是一个 smart_pointer的类型.monitor类型本质上需要开始和结束两个方法,把它封装进ScopedType,依靠ScopedType的构造和析构来完成.这样做的目的是避免对限制monitor的方法名称,可以看作是traits手法的简化版本.你可以自定义合适的ScopedType类型.嵌套类proxy的构造函数的隐式转换是必要的,它可以消除额外的copy ctor的需求.
  上述的实现代码已经没什么神奇之处可言了.使用方法如下:
typedef vector<int> MyVector;
MyVector cont;
LockType lock;
typedef call_proxy<MyVector*, LockType, LockType::scoped_lock> proxy_type;
proxy_type cont_proxy(&cont, lock);
至此,cont_proxy可以作为一个封装好的对象使用了.嗯,当然,cont_proxy的生命周期应该比cont来得短,这个问题就让程序员去保证吧.
剩下的问题:
  call_proxy还有一些问题需要解决.我们在调用某些成员方法的时候,未必都要加锁.如果对象和锁是分离的,那么自然很容易处理.如果是手工封装,虽然工程浩大,但是也可以在适当的地方加以特别处理.特别的,对于分离的对象和锁,我们还可以使用大粒度的锁定过程,从而改善某些性能.而这里的 call_proxy则没有这种灵活性,当然手工封装的类也无此灵活性.然而,我们还是可以改善call_proxy,从而在一定程度上获得这种灵活性的好处,为call_proxy增加两个友元方法:
friend pointer_type getImp(const self_type& cp){ return cp.m_obj;}
friend monitor_reference getMonitor(const self_type& cp){ return cp.m_monitor;}
当我们需要大粒度的机制时可以这样:
{
proxy_type::scoped_type guard(getMonitor(cont_proxy));
getImp(cont_proxy)->XXX1();
getImp(cont_proxy)->XXX2();
...
}
对于新手,可能会奇怪getImp的用途,或者忘记调用,这不够优雅.但是这样的解决方案已经比较简单了,它简化了大部分的情况,而且留了一条优化的后路.
...全文
6129 164 打赏 收藏 转发到动态 举报
写回复
用AI写文章
164 条回复
切换为时间正序
请发表友善的回复…
发表回复
wuyapu 2009-01-16
  • 打赏
  • 举报
回复
高人,好用
flygh123 2008-06-06
  • 打赏
  • 举报
回复
看过深入浅出MFC才知道,宏用好了是能带来巨大效益的~
hslinux 2008-03-16
  • 打赏
  • 举报
回复
jf
billy1985 2008-03-10
  • 打赏
  • 举报
回复
路过 MARK一下
楼主强人

aiangela 2008-03-06
  • 打赏
  • 举报
回复
ding
michney 2008-03-06
  • 打赏
  • 举报
回复
我喜欢jf
不喜欢奇技淫巧
baihacker 2008-03-06
  • 打赏
  • 举报
回复
mark,收藏
haierjodn 2008-03-06
  • 打赏
  • 举报
回复
太淫了这
rover___ 2008-03-05
  • 打赏
  • 举报
回复
太有才了
tang_cheng 2008-02-17
  • 打赏
  • 举报
回复
发表于:2007-04-05 18:21:4442楼 得分:0
回楼主关于多线程的问题:
多线程的好处确实是实实在在的,就在前两天,我们这一个程序员好兴致勃勃地要用多线程来改善性能,这对他来说是最最方便,门槛最低的改造了。不过在随后的几天受挫和煎熬中,他还是坚决的抛开多线程,改用异步非阻塞的方式来做了。
==============================================
我的经历正好和你相反,我以前是用异步SOCKET来解决问题的,现在才改用多线程。对于处理大量功能相同或相近的操作来说,多线程绝对比异步来得有效的多.
CQZE 2008-02-13
  • 打赏
  • 举报
回复
同步的问题用一些技巧可以轻松搞定。可死锁问题就没那么容易了。
一生有爱1980 2007-12-30
  • 打赏
  • 举报
回复
mark
tcxjia 2007-12-17
  • 打赏
  • 举报
回复
mark
yys5566 2007-12-17
  • 打赏
  • 举报
回复
C++ JAVA 技术群:45609427
提出问题,挑战技术,呈请加入!
ggggfjeicfh 2007-12-15
  • 打赏
  • 举报
回复
要是我有这么个师傅就好了。
kavinwell 2007-12-07
  • 打赏
  • 举报
回复
支持学术交流,真理越编越明!~
ar_2002 2007-12-07
  • 打赏
  • 举报
回复
有才 mark
pptor 2007-11-27
  • 打赏
  • 举报
回复
学习
z_kris 2007-11-27
  • 打赏
  • 举报
回复
好贴 记号!!
maxx 2007-11-13
  • 打赏
  • 举报
回复
有个疑问
typedef vector <int > MyVector;
MyVector cont;
LockType lock;
typedef call_proxy <MyVector*, LockType, LockType::scoped_lock > proxy_type;
proxy_type cont_proxy(&cont, lock);
改成
typedef vector <int > MyVector;
LockType lock;
typedef call_proxy <MyVector*, LockType, LockType::scoped_lock > proxy_type;
proxy_type cont_proxy(lock);
发现这样最终也能执行 MyVector的成员函数,这时MyVector没有实例化而且call_proxy里放的都是MyVector的指针?
到底是哪实例化的呢?????
加载更多回复(143)

5,530

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 模式及实现
社区管理员
  • 模式及实现社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧