不亦快斋

非典型秃子的老窝

用户操作
[即时聊天] [发私信] [加为好友]
非典型秃子
最近评论
qpzkzp:wow gold
overcomeunicom990702:template<typename T, typename U>
bool is_nullptr( T U::* x)
{
return !x;
}
啥编译器能够正确处理成员函数指针?为验证,我在dev-cpp 4.9.9.2上编译不过
C++0x的话用上variadic template就能解决问题,在不支持v……
robotom:凡是涉及知识产权的东西都没有二手货的概念。
tatbaby:"bool值之间只应该做bool运算,永远不要用来做其他运算--除非你知道。"长见识了。。。虽然我用的vc7是支持true之间比较的。
另外,你举的这个例子太不够说服力:
int i = 10;
cout << static_cast<bool&>(i) == true;
它在我vc7上编译都不通过(事实上逻辑上也有点问……
5010329100:可以参考 《vc对象模型》一文 这样就搞的更透彻了。虚函数表的第一项存放着到相应类对象首地址的偏移量
文章分类
收藏
相册
program
友情链接
C++之霓服华裳(RSS)
Donews
laomai的专栏(RSS)
pacman2000的专栏
steedhorse的专栏(RSS)
一块积木(RSS)
废人废语(RSS)
绅士亦花心
辣子鸡丁::天水涧
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 奇技淫巧C++之方法代理收藏

新一篇: f (p == NULL), if (p == 0) 还是 if(p) ?----再论空指针检测问题 | 旧一篇: 恐怖的代码,以及转载

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

author:非典型秃子

  如果你有编写多线程程序的经历,遇到过需要共享对象的情况吗?比如,你想在两个线程中操作同一个容器,你会怎么做呢?是在每个地方都小心翼翼地加锁,还是封装一个来用?两种方法我都用过,但我比较青睐封装一个的办法,例如,封装一个带锁的队列,用于线程间的通信.
  让我们先看看直接操作锁和对象的代码:
//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的用途,或者忘记调用,这不够优雅.但是这样的解决方案已经比较简单了,它简化了大部分的情况,而且留了一条优化的后路.
 
ps:
这种技巧性的东西不是软件开发的根本,不过是自己手痒而已,愿看到此文的程序员一笑而过.

发表于 @ 2007年03月28日 12:22:00|评论(loading...)|编辑

新一篇: f (p == NULL), if (p == 0) 还是 if(p) ?----再论空指针检测问题 | 旧一篇: 恐怖的代码,以及转载

评论

#eXile_ 发表于2007-03-28 23:25:34  IP: 218.15.154.*
关于加锁, boost 网络库 asio 中的 strand 是另外一种思路
#wingfiring 发表于2007-03-29 10:20:40  IP: 202.95.81.*
对asio还不了解。大概看了一下文档。
从文档的说明来看,strand大致是把handler放到一个线程中去执行,并且保证串行调用,因此,对于handler实际上是一个delegate,因此必须(或说多数情况要)借助于boost::bind产生一个function object.有点类似active objects的手法。boost Vault/Concurrent Programming下的defer倒也实现了一个类似的机制。

不知道理解得是否正确?
#eXile_ 发表于2007-03-29 11:40:22  IP: 218.15.154.*
(1)strand只是保证handler的串行化执行, 线程分派是其它类的功能.
(2)关于方法代理, 我的工具箱里也有一个locked_ptr, 它利用临时对象的生命期
template <class Obj, class Mtx = thread_mutex>
struct locked_ptr
{
locked_ptr(Obj* obj, Mtx& mtx) : obj_(obj), mtx_(&mtx) { mtx->lock(); }
~locked_ptr() { mtx->unlock(); }

Obj* operator-> () { return obj_; };

Obj* obj_;
Mtx* mtx_;
};

template <class Obj, class Mtx>
inline
locked_ptr<Obj, Mtx> make_lock(Mtx& mtx, Obj* o)
{
return locked_ptr<Obj, Mtx>(o, mtx);
}
可以这样用(可用逗号来保证对象的有效性):
vector<int> aVector;
make_lock(aMutex, &aVector)->push_back(1), aVector.push_back(2);
#wingfiring 发表于2007-03-29 13:19:46  IP: 202.95.81.*
eXile_, 可不可以这样理解,把你的make_lock改造成class, 返回值通过operator->提供,就和我的实现是类似的?
#holyfire 发表于2007-04-02 09:29:36  IP: 202.136.211.*
看看ACE的Atomic_Op
#zhuzhu101011 发表于2007-08-31 16:03:20  IP: 218.107.252.*
论公告:
如果鸡蛋并不是很多人吃,也没权威验证过,你还想不想去看它的成分呢
发表评论  


当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
Csdn Blog version 3.1a
Copyright © 非典型秃子