zoukankan      html  css  js  c++  java
  • Immutable模式与string类的实现

    Immutable模式与string类的实现  梗概
    禁止改变对象的状态,从而增加共享对象的坚固性、减少对象访问的错误,同时还避免了在多线程共享时进行同步的需要。

    实现方法:在对象构造完成以后就完全禁止改变任何状态信息。如果需要改变状态,则生成一个状态与原对象不同的新对象。

    场景
    假设你正在为一家游戏公司开发一个和外太空、宇宙飞船有关的游戏,当然你有必要用某种方式来表示一艘宇宙飞船(不管它是属于地球人的还是属于外星人的)所处的位置。很自然的,你决定编写一个Position类。从一个Position对象应该可以查询到当前位置的x坐标和y坐标(我们的游戏比较简单,二维地图,呵呵),还应该可以根据输入的偏移量得到新的位置。很正确的设计,不是吗?(见例1)

    例1:Position的设计

    class Position{
        private:
           int x, y;     //简单点,用整型数来表示坐标
        public:
           Position(int x, int y){  //ctor需要两个参数。
               this->x = x;
               this->y = y;
           }
           int getX( ){ return x; }
           int getY( ){ return y; }
           void Offset(int offX, int offY){  //根据偏移量得到新的位置
               x+=offX;
               y+=offY;
           }
    }

    但是,如果我们的Position需要在多线程环境下使用,它能保证线程安全吗?答案是很明显的No!如果两条线程同时调用同一个Position对象的Offset函数,你就无法保证得到的结果是什么了。所以,为了保证线程安全,也许你还会想给Offset函数加上同步机制——麻烦了!
    换个角度想想怎么样?假如我们根本不让Offset函数修改Position的内容?假如我们让Offset函数生成一个新的Position对象?如果是这样,Position对象就已经是线程安全的了——它没有任何“写”操作,而没有写操作的类是不需要同步的。于是我们这样做了,并且很轻松的得到了一个线程安全的Position类。(见例2)

    例2:线程安全的Position类(这里只展示Offset函数)

    Position Position::Offset(int offX, int offY){  //根据偏移量得到新的位置
        return Position(x+offX, y+offY);
    }

    约束
    你有一个天性被动的类。这个类的实例不需要改变自己的状态。同时这个类的实例还被其他多个对象共享。
    正确协调被共享的对象的状态改变非常困难。当一个对象的状态发生改变时,所有使用它的对象都应该得到通知。这造成了对象之间的紧耦合。
    在多线程共享时,还需要使用同步机制来保证线程安全性。
    解决方案
    为了避免状态改变带来的诸多麻烦,不允许对实例的状态做任何修改。具体的做法就是:不在类的公开接口中出现任何可以修改对象状态的方法,只出现状态读取方法。如果client需要不同的状态,就生成一个新的对象。(见图1)

    图1 Immutable模式的类图

    效果
    不再需要协调状态修改的代码,也不再需要协调任何同步代码。
    生成了更多的对象。增加了对象生成和销毁的开销。
    实现
     
    Immutable模式的实现主要有以下两个要点:

    1.除了构造函数之外,不应该有其它任何函数(至少是任何public函数)修改任何成员变量。
    2.任何使成员变量获得新值的函数都应该将新的值保存在新的对象中,而保持原来的对象不被修改。

    在“效果”中我已经讲到:Immutable模式会大大提高对象生成和销毁的频率。因此,在C++中实现Immutable模式时,还必须特别注意对象的生存周期。你可以尝试用智能指针[Meyers96, Item28]来帮助你处理对象的销毁问题,但是无论如何你都必须仔细检查以确保没有内存泄漏——如果每艘飞船的每次移动都会造成内存泄漏,你的游戏该是多么糟糕!

    此外,Immutable模式还有一种变体:Read Only Object模式。它的做法是:当一个类的对象对于某些client可写、某些client不可写时,让这个类实现一个ReadOnly接口。然后让可写的client直接访问对象,而让不可写的client通过ReadOnly接口访问该对象,从而实现了不同的读写权限控制。(如图2所示)

    图2 Read Only Object模式

    Immutable模式与string类的实现策略
    如果你也读过[Meyers96],我想你一定对那个应用在String类上的COW(Copy-On-Write)策略[Meyers96, Item29]印象深刻。COW策略是“lazy evaluation”的发展形式。如果对String类的写操作数量很少,那么COW策略将大大提高整个String类的效率,并大大降低空间开销。

    可是你知道吗?在STL中的string类并没有采用COW策略,从例3就可以看出这一点。为什么?为什么这么好的策略没有得到采用?相信你从[Meyers96]中便可发现:实际在String类上实现COW策略是如此复杂。更何况我们还必须考虑线程安全的问题。我完全有理由认为:正是因为考虑到这些复杂的情况,STL的实现者们才最终决定用一个比较低效但是安全的实现方案。

    例3:STL中的string::operator=和string::operator[]

    //下面代码出自SGI STL 2000年6月8日版本
    //为了帮助读者理解,我做了些微改动,并在关键位置加上注释

    //如果使用COW策略,operator=应该不做内容复制,而是进行引用计数
    string& string::operator=(const string& s) {

     if (&s != this)
          assign(s.begin(),s.end()); // 这里的operator=只是简单的内容复制而已
        return *this;
    }

    //如果使用COW策略,const的operator[]和非const的operator[]应该不同
    //但是这里两个operator[]完全相同
    const char & string::operator[](int n) const
        { return *(_M_start + n); }     //_M_start是字符数组的起始位置
    char & string::operator[](int n)
        { return *(_M_start + n); }

    看到这些,我不能不开始猜想:为什么STL的设计者们一定要保留这些给他们造成麻烦的“修改函数”(即可以修改string内容的函数)?我想,这是因为他们希望让string的行为方式尽量接近于C语言的char *型字符串。不然,我真的想不出其他任何保留operator[]的理由。

    那么,如果不必非要让string类的行为方式接近char *型字符串,如果string类的读操作应用频率远远大于写操作(在实际应用中这是很常见的),你会考虑如何实现一个string类?啊,也许你已经想到了:Immutable模式。你可以很舒服的使用[Meyers96]教你的引用计数方法来节约存储空间,你不必再担心写操作的同步问题或别的什么,因为已经没有写操作。任何改变字符串内容的操作都将得到一个新的string对象。而对象生存期管理和存储空间管理这两个大问题也因为Immutable模式的引入而大大简化,你完全可以参照[Meyers96]第183页到第189页的内容自己来解决它们。

    代码示例
    我用了一天的时间,做了一个简单的ImmutableString实现。其中实现细节用了Proxy类[Meyers96],并参考了COM的引用计数规则[Pan99]。在这个例子中,读者可以感觉到:Immutable模式大大简化了共享空间的字符串类型的实现,并为其中的一些方法(比如subString)的实现提供了非常大的便利。本来我想把代码放在文章里面,但是时间和空间受限,最后决定放弃。

    在该代码中,我做了一个简单的效率测试:反复进行字符串对象的赋值(operator=)操作。结果表明:ImmutableString的效率比std::string高出了一倍左右。假如你的业务就是不断的读取数据库、不断的赋值、不断的输出,而不对字符串进行修改,那么ImmutableString的效率提升是非常可观的。

    该示例代码在VC .NET下编译通过。

    相关模式
    经常会使用Abstract Factory模式[GOF95]来创建新的对象。

    大量的对象经常通过Flyweight模式[GOF95]被共享。

    参考书目
    [Meyers96] Scott Meyers, More Effective C++, Addison-Wesley, 1996.

    [GOF95] Erich Gamma etc., Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995. 中译本:《设计模式:可复用面向对象软件的基础》,李英军等译,机械工业出版社,2000 年9月。

    [PAN99] 潘爱民,《COM原理与应用》,清华大学出版社,1999年11月。

  • 相关阅读:
    Educational Codeforces Round 86 (Rated for Div. 2) D. Multiple Testcases
    Educational Codeforces Round 86 (Rated for Div. 2) C. Yet Another Counting Problem
    HDU
    HDU
    HDU
    HDU
    Good Bye 2019 C. Make Good (异或的使用)
    Educational Codeforces Round 78 (Rated for Div. 2) C. Berry Jam
    codeforces 909C. Python Indentation
    codeforces1054 C. Candies Distribution
  • 原文地址:https://www.cnblogs.com/sunwei2012/p/1689074.html
Copyright © 2011-2022 走看看