第三章,异常

条款17 考虑使用lazy evaluation(缓式评估)

(1)尽可能延迟提供副本,COW思想,数据共享(多个指针指向听一片内存,引用计数获取对应数据)

String s1 = "Hellow";
String s2 = s1; //直接拷贝会产生不必要的副本,可直接让他们共享同一片内存,通过引用计数和指针来获取内存中指定的数据,这样就避免了参数不必要副本的问题

(2)读时可以使用refernce-counter思想提效,而写时不得不进行拷贝动作,那如何判断 opertator[] 是读还是写呢?见条款30 proxy classes

String s = "Homer's Iliad";
...
cout << s[3]; //operator[] 读,不需要进行拷贝
s[3] = 'x'; //operator[] 写, 不得不执行拷贝

(3)例子: Lazy Fetching(缓式取出)
想象你的程序使用大型对象,其中内含许多字段。
如此对象必须在程序每次执行时保持与前次执行的一致性与连贯性,
所以它们必须存储于一个数据库中。每个对象有一个独一无二的对象识别码,可用来从数据库中取回对象

class LargeObject {        // 大型的、可持久存在的(persistent)对象。
public:
LargeObject(ObjectID id); // 从磁盘中回存对象。

const string& field1() const; // 字段 1 的值。
int field2() const; // 字段 2 的值。
double field3() const; // ……
const string& field4() const;
const string& field5() const;
...
};

void restoreAndProcessObject(ObjectID id)
{
LargeObject object(id); // 回复对象。

if(object.field2() == 0){ //从磁盘中获取整个数据后再获取一个filed2字段,获取的其他字段变成了浪费
... ... ...
}
}
// 修改
class LargeObject { // 大型的、可持久存在的(persistent)对象。
public:
LargeObject(ObjectID id); // 从磁盘中回存对象。

private:
ObjectID oid;
mutable string *field1Value; //mutable修饰,准许const member functions内也可修改改成员
mutable double *field2Value;
mutable int *field3Value;
...
};

LargeObject::LargeObject(ObjectID id)
: oid(id), field1Value(nullptr), field2Value(nullptr), field3Value(nullptr), ...
{} //

void restoreAndProcessObject(ObjectID id)
{
LargeObject object(id); // 回复对象。

if(object.field2() == 0){
... ... ...
}
}

const string& LargeObject::field1() const
{
if(field1Value == 0){ //缓式取出,需要时才取出
//read the data for field 1 from the database and make fieldValue point to it;
}
return *field1Value;
}

(4)例子: Lazy Expression Evaluation(表达式缓评估)

template<class T>
class Matrix { ... }

Matrix<int> m1(1000,1000);
Matrix<int> m2(1000,1000);
...
Matrix<int> m3 = m1 + m2;

cout << m3[4];//只获取第4行的计算结果

如果operator + 马上就执行计算,那么消耗的资源势必不少,但是如果延迟到真正使用m3的时候呢?毕竟大部分情况下使用者是希望获取m3中某一行或某一列的计算结果;
要实现这种效果,就需要两个执行要运算的矩阵的指针,和一个enum枚举表明将进行什么运算;
但是有一些情况,如:
1、当要求输出全部计算结果的时

cout << m3;

2、指针指向内容改变时

m3 = m1 + m2;
m1 = m4;

应对这些情况,需要存储数值间的相依关系,而且必须维护一些数据结果以存储数值、相依关系,或者是两者的组合,此外还必须将赋值、复制、加法等操作符加以重载。

那么,这么多工作值得吗?
值得,这样的设计通常能够再程序执行时节省大量的时间和空间,在许多应用领域中有这样的收益是值得的。

总结:
1、避免非必要的对象复制,可区别operator[]的读取和写动作;
2、避免非必要的数据库读取动作
3、避免非必要的数值计算动作

注意:
尽管如此,lazy evaluation并非永远是好主意。因为正如计算数值的例子那样,如果所有计算都是必要的,那么lazy evalution起到的效果几乎没有,且由于为了实现lazy evaluation所做的那些工作会占据不少程序内存,反而可能导致程序更加缓慢。
只有在软件被要求执行某些计算,而那些计算其实可以避免时,lazy evalution才会有用处

引申:
APL就是采用lazy evalution来延缓计算,直到确切知道其结果矩阵在哪一部分被真正需要,就只做这一部分的计算;正是这样的伎俩支撑起了APL这个诞生于古老的20世纪60年代的神奇。