zoukankan      html  css  js  c++  java
  • More Effective C++ 条款17 考虑使用lazy evaluation(缓式评估)

    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加入某个类内而对客户隐藏具体实现成为可能.

  • 相关阅读:
    游标,存储过程
    利用IDEA构建springboot应用-如何优雅的使用mybatis
    PLSQL编程
    利用IDEA构建springboot应用-数据库操作(Mysql)
    01-常见Dos命令、Java历史、Java跨平台、配置Path环境变量、第一个HelloWorld例子
    利用IDEA构建springboot应用-Controller的使用
    利用IDEA构建springboot应用-配置文件
    SharePoint和Outlook组合构建日历提醒功能
    SharePoint隐藏快速启动栏_左侧导航_所有网站内容_回收站
    使用SharePoint Designer创建子网站时,无法找到常见模板
  • 原文地址:https://www.cnblogs.com/reasno/p/4830677.html
Copyright © 2011-2022 走看看