返回   cpper编程论坛 > Blog > liuxinyu
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

为这篇文章评分

延迟求值和无穷(3)

发表于 2007-08-15 03:04 AM 作者: liuxinyu
3 Implementation

实际上,延迟求值的实例在计算机应用中也有很多。例如,用户用某个字处理软件 (如MS Word)打开一篇包含图片的文档时,文档中的图片并不一下子全部读出来。 只有那些在第一页的图片才被读出并显示,随着用户向后面翻页。后面内容中的图片 才逐步被打开并显示。
在GoF的着作《设计模式》[6]中,给出了一个proxy模式实现上述延迟求值的例 子。本文将给出一个“超市问题”的延迟求值解法。transform程序和filter程序都具备 足够的抽象,它们是通用的程序,完全可以重用,所以问题的关键是record的实现, 要在record的内部,引入延迟求值。
目前的纪录包含两个部份,一个是该笔交易的金额,另一部分指向下一笔纪录。 所有纪录依次链结下去,形成完整的纪录表。对纪录表引入延迟求值的思路大致如 下:
在构造一笔纪录时,并不要求当前的金额和指向下一笔纪录的指针都准备好。只 要当前的金额准备好就可以构造了。而关于下一笔纪录的信息,直到明确地通 过next命令进行引用时,才真正对其进行求值。
考虑到filter和transform的通用性,延迟求值的引入不应该严重影响到这 两个程序的实现和使用。所以原来程序中的下述语句的应该表现为延迟求 值:
代码:
new record(f(r->value), transform(r-> next, f));
执行这条语句时,transform(r-縩ext, f)并不应该被立即求值,但是根据C++的语 法规则,record构造函数的第二个参数会在运行时进行计算。最简单的解决方案是采 用lambda 算子[3]。考虑有一些读者不了解lambda演算,本文给出一个使用标准C++ 实现延迟计算模型。其思路如下:
首先,为了防止C++在运行时,自动求值record构造函数的第二个参数,先将 此参数设置为空值0, 然后,在给record增加一个函数,该函数用来保存将 来延迟求值时需要使用的transform, r和f,但是仅仅是保存它们,而不立 即进行求值。只有在未来某个时候,用户通过next方法,明确要求获取下 一个纪录值时,才利用以前保存过的transform, r和f,求出下笔纪录的信 息。
才用上述方法,发现也适用于filter程序的下列语句:
代码:
return new record(r->value, filter(r-> next, f));
在filter程序中,需要保存的是filter, r, 和f。通过对transform和filter归纳,大致可 以得出这样的简单结论:为了实现延迟求值,必需保存3个内容,延迟求值表达式所 使用的函数(如transform或filter),延迟求值时所作用的对象(如纪录r),延迟 求值函数所需要的参数(如f,它实际可能是discount函数或者Greator� functor)。
通过以上分析,就可以实现一个支持延迟求值的pos机纪录模型了,为了 和前面普通的record区别,这一新类被命名为stream_record。初步实现如 下:
代码:
class stream_record{ 
public: 
    stream_record():value(0), _next(0){} 
    stream_record(double v, stream_record* r=0):value(v), _next(r){} 
    virtual ˜stream_record(){ 
        delete _next; 
    }
    template<class Func, class Arg >  
    stream_record* setNext(Func f, stream_record* r, Arg arg); 
 
    virtual stream_record* operator()(){ return this;} 
 
    stream_record* next(){ 
        if(_next) 
            _next=(*_next)(); 
        return _next; 
    } 
    double value; 
 
private: 
    stream_record* _next; 
};
stream_record和前面的record相比,有一下这些变化。首先,构造函数 的第二个参数可以省略掉了,缺省时是空指针0。其次增加了一个函数模 板setNext,使用它,用户就可以将延迟求值使用的函数,作用对象,以 及函数参数,以类型自由的形式传入,并保存起来。最后的变化是next成 员方法,如果next不为空,这时可能有两种情况,一种是next指向了一个 真正的已经被求出stream_record,这样的话,就直接通过operator()()返 回下一笔纪录;另外一种情况就是,下一笔纪录尚未求出,next指向的是 延迟求值对象。由于operator()()是虚函数,所以根据多态原理,就会调 用延迟求值对象重载的operator()() ,从而利用先前保存的f, r和arg求出 下一笔纪录,结果以一个普通的stream_record返回,并覆盖原来的next指 针。
根据这一解释,还可以了解到,延迟求值对象,必然是stream_record的派生类, 所以才能够根据Liskov原则,被当作一个strem_record保存在next指针里。同 时,由于延迟求值对象是通过setNext函数模板建立的,所以它必然是一个 类模板对象。根据这些分析,可以把延迟求值对象和setNext最终实现出 来:
代码:
template<class Func, class Arg >  
class delayed_record: public stream_record{ 
public: 
    delayed_record(Func f, stream_record* r, Arg arg): _f(f), _r(r), _arg(arg){} 
    stream_record* operator()(){ 
        stream_record* res=  _f(_r->next(), _arg); 
        delete this; 
        return res; 
    } 

private: 
    Func _f; 
    stream_record* _r; 
    Arg _arg; 
}; 
 
template<class Func, class Arg >  
stream_record* stream_record::setNext(Func f, stream_record* r, Arg arg){ 
    _next  =  new delayed_record< Func, Arg>(f, r, arg); 
    return this; 
}
延迟求值类模板全部从stream_record继承,它在初始化的时候利用构造函数保存 起f, r和arg。延迟求值类模板重载了operator()(),在这里,它利用以前保存好的f, r和arg求出真正的stream_record值,然后就把自己释放掉,以后用户在引用时,就避 免重复求值了。
setNext函数模板,根据用户传入的不同求值函数类型,以及参数类型,构造初延 迟求值对象,并赋值给next指针。
通过上述延迟求值实现,filter和transform可以仅仅做很小的改动,就能处理延迟 求值的纪录了。
代码:
template< typename Record, typename F >  
Record* transform(Record* r, F f){ 
    if(r){ 
        return (new Record(f(r->value))) ->setNext(transform< Record, F>, r, f); 
    } 
    return 0; 
} 
 
template< typename Record, typename F >  
Record* filter(Record* r, F f){ 
    if(r) 
        if(f(r->value)) 
            return (new Record(r->value)) ->setNext(filter< Record, F>, r, f); 
        else 
            return filter(r->next(), f); 
    return 0; 
}
上述改动的关键之处,在于构建新Record的语句,程序首先利用新的value构建一 个next 指针为空的纪录,然后在利用setNext函数,将延迟求值必需的函 数,对象以及参数传入,从而将改纪录的next指针设置为指向一延迟求值对 象。
为了使原先的普通record和支持延迟求值的stream_record都能被transform和filter 处理,可以也在普通的record中略微改动一下:
代码:
class record{ 
public: 
    record(double v, record* r=0): value(v), _next(r){} 
 
    template<class Func, class Arg >  
    record* setNext(Func f, record* r, Arg arg){ 
        _next=f(r->next(), arg); 
        return this; 
    } 
//…
这样,两种record就都能被使用了。测试代码如下:
评论 0 Email文章
评论总数 0

评论

发表评论 发表评论
作者为 liuxinyu 的最新文章

所有时间均为格林尼治时间 +9。现在的时间是 09:06 PM


Powered by vBulletin® 版本 3.7.0
版权所有 ©2000 - 2009,Jelsoft Enterprises Ltd.
(C) Copy Right All Right Reserved 2001 - 2007

Search Engine Friendly URLs by vBSEO 3.1.0