码迷,mamicode.com
首页 > 编程语言 > 详细

More Effective C++ 条款17 考虑使用lazy evaluation(缓式评估)

时间:2015-09-22 23:37:03      阅读:301      评论:0      收藏:0      [点我收藏+]

标签:

1. lazy evaluationg实际上是"拖延战术":延缓运算直到运算结果被需要为止.如果运算结果一直不被需要,运算也就不被执行,从而提高了效率.所谓的运算结果不被执行,有时指只有部分运算结果被需要,那么采用拖延战术,便可避免另一部分不被需要的运算,从而提高效率,以下是lazy evaluation的四种用途.

2. Reference Counting(引用计数)

    如果要自己实现一个string类,那么对于以下代码:

String s1="Hello";
String s2=s1;

    最直接的是采用eager evalutation(急式评估):为s1做一个副本并放入s2内,尽管此时s2的内容和s1并没有不同.

    采用lazy evaluation的思想,可以先让s2分享s1的值,这样就省去了"调用new"以及"复制任何东西"的高昂成本.唯一要做的是一些簿记工作,以记录共享同一内容的各个对象.对s2的任何读操作,只需要s1的值即可,然而,一旦需要对s2的值进行写操作,就不能再做任何拖延,必须为s2做一份真实副本并进行写操作.

    这种"数据共享"的观念就是lazy evaluation:在真正需要之前,不为对象构造副本.在某些应用领域,可能永远也不需要提供那样一份副本,从而提高效率.

3. 区分读和写

    承接于2的策略,如果对自定义的string类进行以下操作:

技术分享
String s1="Hello World!";
String s2=s1;
cout<<s2[0];
cin>>s2[1];
View Code

    那么两次对operator []的调用operator[]的实际行为实际上是不同的,前一个读操作只需要返回对应的引用即可,后一个写操作就需要先对s1做一个副本并放入s2中,然后再返回引用,也就是说,视operator[]用于读操作还是写操作,需要在operator内做不同事情,而要判断operator[]用于读操作还是写操作几乎是不可能的是,但如果利用lazy evaluation和条款30所描述的proxy classes,便可以延缓决定"究竟是读还是写",知道确定其答案.

4. Lazy Fetching(缓式取出)

    对于程序需要使用内含许多字段的大型对象,比如:

技术分享
class LargeObject{
public:
    LargeObject(ObjectID id);
    const string&field1()const;
    int field2()const;
    double field3()const;
    const string& field4()const;
    const string& field5()const;
    ...
}
View Code

    那么对于从磁盘中回复一个LargeObject对象,如果要取出此对象的所有字段,数据库相关操作成本可能极高,尤其是如果这些数据需要从远程数据库跨越网络而来.但如果只需要该对象的某个或某几个字段,那么读取所有数据的操作其实是不必要的.

    采用Lazy evaluation的思想,在产生一个LargeObject对象时,可以只产生该对象的"外壳",而不从磁盘读取任何数据.只有当该对象的某个字段被需要时,才从数据库中取回对应的数据,以下做法可以实现这种"demand-page"式的对象初始化行为:

技术分享
class LargeObject{
public:
    LargeObject(ObjectID id);
    const string&field1()const;
    int field2()const;
    double field3()const;
    const string& field4()const;
    const string& field5()const;
    ...
private:
    ObjectID oid;
    mutable string *field1Vaule;//注意使用了mutable修饰符
    mutable int *field2Value;
    mutable double *field3Value;
    mutable string *field4Value;
    ...
}
LargeObject::LargeObject(Object  id):oid(id),field1Value(0),field2Value(0),field3Value(3)...{}
const string& LargeObject::field1()const{
    if(field1Value==0){
        read the data from field 1 from the database and make field1Vaule point to it;
    }
    return *field1Value;
}
View Code

    对象内的每个字段都是指针,指向必要的数据,NULL初始值表示该字段未被读入,需要先从数据库读入.将字段指针声明为mutable,可以保证字段指针可以在任何时候都能被更改以指向实际数据,即使是在const成员函数内也一样.有些编译器厂商可能不支持mutable的使用,在const成员函数内可以采用const_cast甚至C转型操作移除this的常量特性并构造一个冒牌this并进行相关操作:

技术分享
const string& LargeObject::field1()const{
    LargeObject* const fakeThis=const_cast<LargeObject*const>(this);
    if(field1Value==0){
       fakeThis->field1Value=the proper data from the database;
    }
    return *field1Value;
}
View Code

5. Lazy Expression Evaluation(表达式缓评估)

    对于以下代码:

技术分享
template<class T>
class Matrix{...}
Matrix<int> m1(1000,1000);
Matrix<int> m2(1000,1000);
...
Matrix<int>m3=m1+m2;
View Code

    对于operator+,通常的做法是eager ecaluation:计算并返回m1和m2的和,这是一个大规模运算,并需要大量内存分配成本.

    采用lazy evaluation的思想,可以先设一个数据结构于m3中,用于标记m3是m1和m2的总和,这个数据结构可能只由两个指针和一个enum组成,前者指向m1和m2,后者用来表示运算动作.假设在m3被使用之前,程序又执行以下动作:

技术分享
Matrix m4(1000,1000);
...
m3=m4*m1;
View Code

    那么便可直接将m3定位为m4和m1的乘积,之前没有用到的矩阵加法操作实际上并没有进行.当然,对m1和m2加和却没有用到的情况比较夸张,但不排除维护过程中程序员更改代码使得m1+m2不被用到的请况出现.

    当然,lazy evaluation在此处还有更大用法——只计算大型运算中需要的部分运算结果.

    对于以下代码:

cout<<m3[4];

    此时不能再使用拖延战术,但也只需要计算m3第四行的值,除此以外,不需要计算其他任何值.实际上,正是这种策略使得APL(20世纪60年代的一款如软件,允许用户以交谈方式使用软件执行矩阵运算)能够快速处理加法,减法,乘法甚至除法.

    当然,有时lazy evaluation并不能起作用,比如如下操作:

cout<<m3;

    或者:

m3=m1+m2;
m1=m4;

    这时就要采取某些措施以确保对m1的改变不会影响m3的值,可以在对m1进行改变之前先对m3求解,也可以将m1的旧值复制一份,然后令m3依从该值等,其他可能会修改矩阵值的情况,也要采取类似措施.

    此外,由于存储数值间的相依关系,必须维护一些数据结构一存取数值,相依关系等,此外还必须将赋值,复制,加法等操作符进行重载,因此lazy evaluation用于数值运算领域也有许多工作要做,但与节省的时间和空间相比可能是微不足道的.

6. "Lazy evaluation在许多领域中都可能有用途:可避免非必要的对象复制,可区别operator[]的读取和写操作,可避免非必要的数据库读取动作,可避免非必要的数值计算动作."但其提升效率的前提是(部分)计算可能可以被避免,否则,在计算绝对必要的情况下,使用lazy evaluation不仅不能提升效率,还需要付出为lazy evaluation而设计的额外的数据结构等代价.

7. "Lazy evaluation并非C++的专属技能.这项技术可以用任何一种程序语言完成,有数种语言——APL,某些Lisp版本,以及几乎所有的数据流(dataflow)语言——已接受这个观念成为语言的一个基础部分."不过由于C++对封装性质的支持使得将lazy evaluation加入某个类内而对客户隐藏具体实现成为可能.

More Effective C++ 条款17 考虑使用lazy evaluation(缓式评估)

标签:

原文地址:http://www.cnblogs.com/reasno/p/4830677.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!