Lookof 's Wild

Last of the Wild

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

免责声明:速记,真的是速记。用自己的理解把最近一周习得的知识梳理一遍。观者自斟。欢迎批评。 

  • 在操作系统层面。
    最近项目组分配给我一个业务需求,简单说就是在loading大文件之前,能先查阅一下当前内存的使用量,以及结合待loading文件有限的信息做一个大致评估,看看会不会改文件会把内存搞crash。为了应付这个差事,顺道了解了一点点OS底层的内存管理知识。
    • 内存分页(memory paging)是为了统一虚拟内存和物理内存而使用的一种内存管理手段。
    • (在linux下)当你编写的程序调用free()函数试图释放指定内存时,该内存并不会立刻被归还到操作系统中,而是依然被当前程序所持有。不过,该段内存被“protected”,当下次程序需要分配内存的时候,会首先使用这些“protected”的内存,必要时才会再从memory pool中申请。当然,这一设定是可以通过修改底层的内存映射(map)方式改变的。
    • 内存块(block)中有内存的实际内容(即数据),也有指示这块block本身的信息(比如大小),否则当你释放一个block时,OS就无法知道要是放多大的空间。可以理解这种信息是一种header。而这个header本身也是占内存空间的。说明这点是有什么意义呢?意义是,当你的程序分配了数量极多而本身体积又很小的objects时,按OS默认的内存管理方式是很浪费内存的。因为那些海量的header信息也占了相当可观的用量。为此,需要一种专门的“object pool”的内存管理技术来处理这种问题。典型的解决案例是boost::pool.

 

  • 在C++对象模型层面。
    最近在读《深度探索C++对象模型》一书,感觉现在稍微能看懂点了。兴奋地显摆显摆理解。
    • c++直接支持三种“程序设计典范”(programming paradigms): 1) 程序模型(procedual model), 2) 抽象数据类型模型(abstract data type model, ADT), 3) 面向对象模型(object-oriented model, OOM)。 其中,ADT模型强调的是对象的封装,当你在用一个明确的对象类型时(比如关心它的数据,或者调用它的方法时),其实你用到的就是这个模型。有人说这个模型强调的是“基于对象(object based)”的概念。而OOM模型强调的是多态(polymorphism)。当你通过基类设计的接口,才具体操纵(管理)各种子类的行为时,其实你用到的是这个模型。有人说这个模型强调的是“面向对象(object oriented)的概念”。
    • c++通过三种方式支持多态。1) 对一个derived class pointer通过隐式转换为base class pointer。 2) 经由virtual function 机制,将基类行为映射成具体的子类行为。 3) 经由dynamic_cast和typeid运算符,判断与转化意图中的子类对象。
    • c++的虚函数(virtual function)实际上由一个虚表(vitural table, 简称vtbl)管理,而该表是由绑定在对象上的虚指针(vptr)索引的。vtbl是一个存储着对象多态信息的数组。 第一个元素(vtbl[0])一般是对象的类型信息(比如用dynamic_cast做判断时实际上取用的这里的信息),第二个元素开始依次类推是每个子类型重写过的虚函数(比如虚拟析构函数,等等)。vtbl是使用多态时额外带来的空间和时间上的主要开销。空间上表现为存储对象时会占用更多的内存;时间上表现为查找正确的虚函数地址或者真正的类型信息时所花费的时间。
    • 引用(reference)和指针(pointer)在编译器层面实际上是一回事。所以你也可以通过reference来使用多态。也只有这两种方式才可以使用多态。
    • 如果用户设计某个类型时没有提供一个默认构造函数,编译器会默认为用户提供一个吗?答案是——不尽然。编译器只有在它认为“必要”的时候才会为用户提供。何为“必要”?有四种时机:1)“带有default constructor”的member class object. 2) "带有default constructor"的base class. 3) “带有一个virtual function”的class. 4)“带有一个virtual base class”的class。 在这些时机中被提供出来的默认构造函数,是为了满足编译器的需要,即需要初始化以上提到的那些必要的数据对象。用户自己的类中,只有那些“提供default constructor”的成员对象才会被初始化,其他数据都不会被自动(隐式)初始化。另一方面,如果用户提供了默认构造函数,但又没有显式初始化上面所列的四种时机中的数据时怎么办? 答案是——编译器会背后修改用户的代码,在构造函数中添加一些代码,以使那些数据可以按预期初始化。
    • 我自己在阅读过程中突然想到的一个问题:假如你打算通过一个对象的指针索引到对象的具体某个成员时,编译器底层究竟是怎么做的呢?毕竟,你拿到的只是该对象的一个基地址,编译器怎么知道该对象有多大,而内部什么成员又在什么地址呢?这些信息是不是存储在对象内部的什么地方?经过一番资料的探索,答案是,对象内存中并没有存储任何有关这方面的信息。在编译器将代码化为汇编语言的时候,对象成员的地址就经过自动寻址映射到了该成员真正所在的地址上,即被“硬编码”到了底层。因此这个问题,实际上在代码被编译后就不复存在了。

 

  • 在设计模式层面。
    • 我在自己的业余实践项目中使用了观察者(observer)模式。这个模式的确优雅而有效地解决了“对象间通信”的问题。注意一个对象既可能是subject,同时也可能是observer。往往对象都有接收消息然后在根据需求散播消息的需求。
    • 另外我也是使用了桥接(briage)模式的一种形式,用来解决开发过程中“重度依赖某第三方引擎\组件\库”的问题。第三方库的设计往往自成一套,继承体系清晰而完整。但如果你打算自己也设计一套自顶向下、清晰完整的架构,同时又使用第三方库来完整真正的工作。桥接模式是一种比较好的思路。其实在这个问题上,你是“继承”第三方库的元件还是“包含”第三方库的元件,都是一种选择。但“包含”的方案更加具有弹性,未来的架构也会越走越宽。“桥接模式”就是在自己架构中的基类元件中添加一个第三方库元件的指针来实现“搭桥”。实践中,在自己的架构树中免不了有些“共通”的数据需要抽象出来做接口类。另外,桥接模式在某种形式上比较像“适配器(adaptor)模式”,共同的地方都在于“包含元件”,只不过adaptor模式不强调那座“桥”,只强调通过“继承自己的接口并包含第三方库的元件”,来实现“转接”的目的。
    • 另外读到了迭代器(iterator)模式和组合(component)模式。由于尚未有需求来应用,因此缺乏实践的体会。这里扯一两句阅读体会:迭代器模式是将“遍历”的方式抽象出来供给给外部使用,内部则可以随心俗语变动,即所谓的需要关注”变化“的部分。而组合模式其实我们在了解一个架构中时会经常遇到,比如orge的场景组织方式,比如cocos2d-x的scene-layer组织方式。说到底它们都是一棵”树“。树的节点(node)可以使另外一棵子树,也可以使一片叶子(leaf)。叶子将是树的末端,不会再向下衍生出别的东西。它是这棵树的原子数据。


  •  日记今天。
    • 今天会面了一发小哥们(来上海出差,居留数月)。茶余饭后,送别聊天。体察到做哪行都不容易,也都有各自的门道讲。正所谓”隔行如隔山“。作为程序猿,我的确将技术(以及行业相关)上的探索作为一种带点自豪感的享受,但也因此容易”目空一切“,以为自己所了解的东西才是”重要的“,其他世间种种,与我何干。但细想来,怎么可能。我眼界之狭隘,如坐井观天之蛙,又如半瓶子晃荡的醋,实在是可笑。要不得,要不得。
    • 饭吃半饷,此哥们才道今天是他生日,我说怎么不早说。他说生日要看过得有否意义,而不在乎是否吃到了蛋糕。同意。所以祝他生日快乐。
    • 再过几天也是lp生日了。这里提前祝她生日快乐。希望我们2013年的路走得更美满一点,更如愿一点。

 

 

posted on 2013-02-02 23:13  lookof  阅读(504)  评论(0编辑  收藏  举报