为了提高C++编译器的运算效率,我们提出了懒惰计算法,即lazy-evaluation,当你使用了lazy evaluation之后,采用这种方法的类将推迟计算工作直到系统需要这些计算的结果。lazy-evaluation应用于多个领域,我们将分为四个部分进行阐述:
1)引用计数:
class String{...};
String s1="Hello";
String s2=s1;//调用string的拷贝构造函数
通常string拷贝构造函数能让s2被s1初始化之后,s1和s2都有自己的“Hello”拷贝,这种方式将带来较大的开销,因为这种拷贝操作既耗费时间有耗费内存,lazy-evaluation就是不赋值s1一个拷贝,而是让s2和s1共享一个值,我们只需要做一些记录指导谁在共享什么,就可以节省掉调用new和拷贝字符的开销,现在:
cout<<s1;//读s1的值
cout<<s1+s2;//读s2+s1的值
这样也不会进行拷贝复制操作,共享就可以了,然而此时:
s2.convertToUpperCase();
这时候由于s2的值被修改了,而不是连s1的值一起修改,所以此时,string的convertToUpperCase函数应该制作s2的一个拷贝,在修改钱吧这个私有的值赋值给s2,此时就需要进行赋值拷贝操作了。
2)区别对待读取和写入:
String s="Homer's Iliad";
...
cout<<s[3];
s[3]='x';
因为此时如果读取s[3]的时候显然就可以进行lazy-evaluation,但是需要更改s[3]的内容时候,这时候显然需要拷贝了吧,为了能够这样做,我们需要在operator[]中能够区别对待读取和写入操作,我们怎样判断operator[]是读取还是写入操作呢?呃呃呃,我们没法判断,通过调用lazy-evaluation和后面降到的proxy class,我们可以推迟做出是读操作还是写操作的决定,直到我们能判断出正确的答案。
3)lazy fecthing(懒惰提取)
class LargeObject{
public:
LargeObject(ObjectID id);
const string& field1() const;
int field2() const;
double field3() const;
const string& field4() const;
const string& field5() const;
...
};
现在我们考虑一下从磁盘中恢复LargeObject的开销:
void restoreAndProcessObject(ObjectID id){
LargeObject object(id);
...
}
因为LargeObject对象很大,为这样的对象获取所有的数据,数据库的操作开销将非常大,在这种情况下,不需要读取所有数据。
void restoreAndProcessObject(ObjectID id){
LargeObject object(id);
if(object.field2()==0){
cout<<"OBject"<<id<<":null field2.\n";
}
}
这里仅仅需要获取filed2的值,所以为获取其他字段而付出的努力都是白费,当LargeObject对象被建立的时候,不从磁盘上读取所有数据,这样懒惰发解决了这个问题,不过这时候建立的仅仅是一个对象的“壳”,当需要某个数据时候,这个数据才被从数据库中取回。
class LargeObject{
public:
LargeObject(ObjectID id);
const string& field1() const;
int field2() const;
double field3() const;
const string& field4() const;
...
private:
ObjectID oid;
mutable string* field1Value;
mutable int* field2Value;
mutable double* field3Value;
mutable string* field4Value;
...
};
LargeObject::LargeObject(ObjectID id):oid(id),field1Value(0),field2Value(0),field3Value(0),...{}
const string& LargeObject::field1() const{
if(field1Value==0){
//c从数据库中为field1读取数据,使field1Value指向这个值;
}
return *fieldValue;
}
实现lazy Fetching时,你面临着一个问题:在任何成员函数中都有可能需要初始化空指针使得其指向真是的数据,包括const函数中,当你试图在const成员函数中修改数据时候,编译器就会出现问题,最好的方法是声明字段指针为mutable,这表示该属性可以再任何函数中被修改,包括const成员函数中。
这种mutable属性可能并不被你的编译器所支持,这样的话,你就需要另外一种方法实现相同的效果,例如“fake this”:
class LargeObject{
public:
const string& field1() const;
...
private:
string* field1Value;
...
};
const string& LargeObject::field1() const{
LargeObject* const fakeThis=const_cast<LargeObject* const>(this);
if(field1Value==0){
fakeThis->field1Value=the appropriate data from the database;
}
return *field1Value;
}
4)懒惰表达式计算:
template <class T>
class Matrix{...};
Matrix<int> m1(1000,1000);
Matrix<int> m2(1000,1000);
...
Matrix<int> m3=m1+m2;
这种情况下面,lazy evaluation建立一个结构表示m3的值是m1和m2的和,然后用一个enum表示它们间的加法操作,建立这个结构很明显将比m1与m2相加要快许多,也能节省大量内存。然而此时我们考虑这种问题:
Matrix<int> m4(1000,1000);
...
m3=m4*m1;
这时候我们同样不用进行乘法运算,因为我们是lazy evaluation的,此时假设我们初始化m3的值为m1和m2的和,然后:
cout<<m3[4];
这时候,lazy evaluation就无法整体懒惰了,因为需要计算m3的第4行的值,但是仅仅只是第4行的值而已,其余部分不用计算,这就是局部懒惰啦!同时,以下这种情况lazy evaluation也要放弃啦!
m3=m1+m2;
m1=m4;
这里我们必须确保m1更新之后不会改变m3的值,因此,m3在m1被更新之前需要被计算出来,lazy evaluation放弃!