lazy evaluation(懒惰计算法)
More Effective C++----(17)考虑使用lazy evaluation(懒惰计算法)
https://blog.csdn.net/qianqin_2014/article/details/51330339
从效率的观点来看,最佳的计算就是根本不计算,那好,不过如果你根本就不用进行计算的话,为什么还在程序开始处加入代码进行计算呢?并且如果你不需要进行计算,那么如何必须执行这些代码呢?
关键是要懒惰。
还记得么?当你还是一个孩子时,你的父母叫你整理房间。你如果象我一样,就会说“好的“,然后继续做你自己的事情。你不会去整理自己的房间。在你心里整理房间被排在了最后的位置,实际上直到你听见父母下到门厅来查看你的房间是否已被整理时,你才会猛跑进自己的房间里并用最快的速度开始整理。如果你走运,你父母可能不会来检查你的房间,那样的话你就能根本不用整理房间了。
同样的延迟策略也适用于具有五年工龄的C++程序员的工作上。 在计算机科学中,我们尊称这样的延迟为lazy evaluation(懒惰计算法)。当你使用了lazy evaluation后,采用此种方法的类将推迟计算工作直到系统需要这些计算的结果。如果不需要结果,将不用进行计算,软件的客户和你的父母一样,不会那么聪明。
也许你想知道我说的这些到底是什么意思。也许举一个例子可以帮助你理解。lazy evaluation广泛适用于各种应用领域,所以我将分四个部分讲述。
引用计数
通常string拷贝构造函数让s2被s1初始化后,s1和s2都有自己的”Hello”拷贝。 这种拷贝构造函数会引起较大的开销,因为要制作s1值的拷贝,并把值赋给s2,这通常需要用new操作符分配堆内存(参见条款8),需要调用strcpy函数拷贝s1内的数据到s2。这是一个 eager evaluation(热情计算):只因为用到string拷贝构造函数,就要制作s1值的拷贝并把它赋给s2。 然而这时的s2并不需要这个值的拷贝,因为s2没有被使用。
懒惰就是少工作。不应该赋给s2一个s1的拷贝,而是让s2与s1共享一个值。我们只须做一些记录以便知道谁在共享什么,就能够省掉调用new和拷贝字符的开销。事实上s1和s2共享一个数据结构,这对于client来说是透明的,对于下面的例子来说,这没有什么差别,因为它们只是读数据:
仅仅当这个或那个string的值被修改时,共享同一个值的方法才会造成差异。仅仅修改一个string的值,而不是两个都被修改,这一点是极为重要的。例如这条语句:
s2.convertToUpperCase();
区别对待读取和写入
继续讨论上面的reference-counting string对象。来看看使用lazy evaluation的第二种方法。考虑这样的代码:
首先调用operator[]用来读取string的部分值,但是第二次调用该函数是为了完成写操作。我们应能够区别对待读调用和写调用,因为读取reference-counted string是很容易的,而 写入这个string则需要在写入前对该string值制作一个新拷贝。
Lazy Fetching(懒惰提取)
第三个lazy evaluation的例子,假设你的程序使用了一些包含许多字段的大型对象。 这些对象的生存期超越了程序运行期,所以它们必须被存储在数据库里。每一个对都有一个唯一的对象标识符,用来从数据库中重新获得对象:
因为LargeObject对象实例很大,为这样的对象获取所有的数据,数据库的操作的开销将非常大,特别是如果从远程数据库中获取数据和通过网络发送数据时。而在这种情况下,不需要读取所有数据。例如,考虑这样一个程序:
这里仅仅需要filed2的值,所以为获取其它字段而付出的努力都是浪费。
( C++ Primer 第五版中指出:我们希望能修改类的某个数据成员,即使是一个const成员函数,可以通过在变量的声明中加入mutable关键字做到这一点)
关键字mutalbe是一个比较新的C++ 特性,所以你用的编译器可能不支持它。如果是这样,你需要找到另一种方法让编译器允许你在const成员函数里修改数据成员。一种方法叫做 “fake this”(伪造this指针) ,你建立一个指向non-const指针,指向的对象与this指针一样。当你想修改数据成员时,你通过“fake this”访问它:( 实质是用const_cast<type>去掉const属性 )
这个函数使用了const_cast(参见条款2),去除了*this的const属性。如果你的编译器不支持cosnt_cast,你可以使用老式C风格的cast:
Lazy Expression Evaluation(懒惰表达式计算)
一切都完了,我们必须计算m3的全部数值。 同样如果修改m3所依赖的任一个矩阵,我们也必须立即计算:
总结
以上这四个例子展示了lazy evaluation在各个领域都是有用的: 能避免不需要的对象拷贝,通过使用operator[]区分出读操作,避免不需要的数据库读取操作,避免不需要的数字操作。但是它并不总是有用。就好象如果你的父母总是来检查你的房间,那么拖延整理房间将不会减少你的工作量。实际上,如果你的计算都是重要的,lazy evaluation可能会减慢速度并增加内存的使用,因为除了进行所有的计算以外,你还必须维护数据结构让lazy evaluation尽可能地在第一时间运行。在某些情况下要求软件进行原来可以避免的计算,这时lazy evaluation才是有用的。