九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

小评《卓有成效的程序员》—《The Productive Programmer》

这不是写给初学编程的人看的书,他们还是好好的听好亘古不变的教诲,“努力学习语言本身,不要被工具所干扰”;这不是写给一般的程序员看的书,他们迷失在各种各样的新兴语言之中;这也不是给一个对编程这种工作没有热情的人看的,因为工作效率再高,对他们来说,也仅仅是被剥削的更加严重。但是,假如你是和我一样,热爱着编程,即便是被剥削至最后一滴血也绝不罢休的提高着自己的工作效率,能够用键盘完成的绝不用鼠标完成,能够点一次鼠标完成就绝不点第二次,能够敲一个键完成的绝不敲第二个键,能够自动化完成的绝不手动完成的程序员的话,这会是你想要看的书,也会是你喜欢看的书,你会发现,原来你还有志同道合者,原来还有人和你一样,懂得怎么艺术性的操作电脑,懂得怎么让电脑干它该干的事情,而不是让自己去干那些重复性的工作,哪怕老板交代的任务多么枯燥和具有重复性,也能有技术的艺术性的完成,而且,比一般人完成还要快一个数量级。

当我看到书的前言部分,我就知道这是我想要看的书,几乎是一口气读完,书不厚,但是沉淀的都是实打实的技术,不是什么奇技Y巧,都是提高工作效率的有用途径,虽然这样说似乎有枪手的嫌疑了-_-!所以,这里还是补充一句。。。。。出版社是我不喜欢的机 工,奇怪的是为什么那么多好书,特别是O’REILLY的书都被他们弄走版 权了,看看那些无奈的蝌蚪文所谓影印版吧,看看那些经典书籍被印在什么样透明的纸上了。。。。。。对于出版社,个人还是更喜欢人民邮电(真的是有责任性的出版社)和清华,电子工业还说的过去。晕,我这个人就是常常不靠谱,说了这么多题外话,但是总的来说,这本书的印刷质量在机工中应该还算中上,不至于像《Windows核心编程》那么恶心。(指的是印刷质量,书绝对是好书)

书中第一部分的机制给出了很多有意思的法则,也提供了很多在不同平台下的实际有用工具,很多东西会让你很有收获,加速器,宏,虚拟桌面等,的确是是实实在在的有用,第二部分相对第一部分来说更加与语言接近了,由于本人的母语是C++,完全没有学过JAVA,Groovy等语言,相对而言很多语言的示例对我来说就没有那么实用及启发性,但是其思想还是在的,虽然个人感觉要在我们公司这样的项目中实行测试驱动开发(TDD)好像不太可能。

总体而言,书中对于命令行的推崇较合我意,对于编辑器的各种评价,包括对本人最爱的VI的很高的评价,包括对正则表达式的评价及使用都和本人自己在工作中的使用感受非常贴近。有的时候,你不知道一件事情,或者你觉得一件事情比较难学,然后就放弃了,其实,放弃的是一片天空。。。。。。我无数次的享受着VI及正则表达式带给我的便利,但是也许我没有办法将其描述的像作者那样吸引人,:)

《小小游戏程序员四个月工作总结》中后面一部分也是我曾经使用过的例子。

另外,这里想说的额外一点就是,正则表达式的使用远远不仅仅是匹配某个数据而已,当你想在一大堆代码中某个特定位置添加某条或某段语句的时候,善用替换的方式,可以节省你大量的时间,这几乎是我解决类似问题的必然途径(作为小程序员,老是会为了减轻总监等的负担而接受这样的枯燥任务),但是,个人感觉,在乏味的工作,都能有艺术性的解决方式。

当然,你按照书中所讲的方式去做,肯定能提高自己的工作效率,但是,首先这是一种思想,起码你要有对提高自己工作效率一种追求,不然,讲的再多也是白费。几乎可以肯定的是,最大的提高工作效率的方式绝对来自你自己工作中的需求,而不是其他人的方式。

我们可以以Larry Wall说的”懒惰、傲慢、缺乏耐性是程序员的三大美德”为准绳,来提高效率。

这看起来似乎不可理喻,但听听解释你就会同意这个说法了。

懒惰:因为好的程序员会致力于减少需要完成的工作量,想尽一切办法减少。

傲慢:容易被荣誉感冲昏头脑,所以会把程序写的尽可能的完美,免得被别人嘲笑。

缺乏耐性:坚决不做重复性的工作,那是计算机应该做的。遇到重复性工作就想尽一切办法来交给计算机做。尽管有时候这可能会花比直接重复一下更长的时间。

我阅读的版本:
卓有成效的程序员

原创文章作者保留版权 转载请注明原作者 并给出链接

九天雁翎(JTianLing) — www.jtianling.com

阅读全文....

多想追求简洁的极致,但是无奈的学习C++中for_each的应用


多想追求简洁的极致,但是无奈的学习C++for_each的应用

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

for each语法是方便的,也是很自然的,这也是为什么很多语言都有这样的语法,就我所知,包括java(jdk5.0以上),python,php,asp.net等语言都有类似的语法,甚至微软为C++/CLI中也添加了这样的语法。但是很遗憾的是,C++98标准中没有,于是,我们只能通过可悲的for_each算法去模拟。。。。。。。。。。先看看原生的语法是多么方便和自然的吧,虽然有人将其视为语法糖,但是,就算是糖,这也是很甜的那种。

先看看Python中的循环,虽然不是for each,但是类似于。

l = [1,2,3,4,5]

for i in l:

print i

简洁,干净,

假如你有幸使用微软的托管C++,你可以使用类似的语法:

using namespace System;

 

#include <list>

#include <iostream>

using namespace std;

 

int main()

{

    int a[5] = {1,2,3,4,5};

    list<int> l(a, a+5);

 

    for each ( int i in l)

       Console::Write(i);

 

 

    system("PAUSE");

}

虽然作为强类型语言,在声明方面稍微复杂点,循环的处理还是那么简洁,干净。

 

再来看看现有的C++中的:

#include <list>

#include <iostream>

using namespace std;

 

int main()

{

    int a[5] = {1,2,3,4,5};

    list<int> l(a, a+5);

 

    // 同样是需要输出

    for(list<int>::const_iterator lit = l.begin(); lit != l.end(); ++lit)

    {

       cout <<*lit <<endl;

    }

 

    system("PAUSE");

}

繁复到我都不想说了,list<int>::const_iterator似的迭代器声明语法不符合一处定义的原则,冗余信息太多。(C++09添加的auto用法就是解决此问题的),即便是解决了此问题,还是会发现,在C++中写个循环比在python(仅仅是一个例子,其他有类似for each特性的语言都比C++简单)中复杂太多了。而循环实在是太过于常见的语法了,所以一次又一次使用这种本可以简单,但是受限于语法而搞得这么复杂的C++可怜语法的时候,我总是忍不住想要吐血。对于这么简单的例子,我们是可以找到一些方法来稍微简化一点的。没有for each语法,我们起码还有for_each算法-_-!

于是可以这样:

#include <list>

#include <iostream>

#include <algorithm>

using namespace std;

 

void printInt(int i)

{

    cout <<i <<endl;

}

 

int main()

{

    int a[5] = {1,2,3,4,5};

    list<int> l(a, a+5);

 

    // 同样是需要输出

    for_each(l.begin(), l.end(), ptr_fun(printInt));

 

    system("PAUSE");

}

 

在加大了理解难度后(本来for each语法多简单啊,现在还要理解ptr_fun这样的函数对象生成的辅助函数),我们的循环是稍微简单一点了,虽然在这个例子中我们甚至要额外写函数-_-!虽然说函数可以只写一次,循环可是常常用的啊。

对于这样简单的例子,已经可以看出没有for_each语法的痛苦了,再复杂一点的例子

 

对于类成员函数的调用,看看有for_each的情况

python中:

class Add():
    def __init__(self, i):
        self._i = i
    def add(self):
        self._i += 1
    def __str__(self):
        return str(self._i)
    
s = [Add(1), Add(2), Add(3)]

for a in s:
    a.add()

for a in s:
    print a

 

这里拆分成两个函数,可以看出我的无奈,想要在一个for_each语法中连续调用两个函数的方法。。。。目前只有再写一个函数,而这个函数的作用就是仅仅调用这两个函数提供给for_each使用。不说这些丧气+无奈的话了,光是调用一个类的成员函数的可能还是有的。

C++中:

#include <list>

#include <iostream>

#include <algorithm>

#include <cstdio>

using namespace std;

 

void printInt(int i)

{

    cout <<i <<endl;

}

 

class CAdd

{

public:

    CAdd(int ai):mi(ai) { }

 

    void add() { ++mi; }

 

    operator int() { return mi;}

    int mi;

};

 

int main()

{

    CAdd a[3] = { CAdd(1), CAdd(2), CAdd(3)};

    list<CAdd> l(a, a+3);

 

    // 同样是需要输出

    for_each(l.begin(), l.end(), mem_fun_ref(&CAdd::add));

    for_each(l.begin(), l.end(), ptr_fun(printInt));

 

    system("PAUSE");

}

为了实现循环的简洁,重新引入了新的复杂度,mem_fun_ref,希望一般的C++程序员见过这样的函数对象辅助函数。。。。还多了类似&CAdd::add这样的成员函数指针的语法,希望一般的程序员也能理解。。。。(不提有for each语法的语言中除了for each这样自然的语法外,做多复杂的运算都没有引入任何新的复杂度),最主要的是,你想要在一条for_each中实现两个函数的调用,你除了老老实实的实现一个新的函数外,就是像我这样了,调用for_each两次,两种方法都是不那么容易让人接受。。。。。。。。但是,在现有的C++中,我们也就只能做到这样了。既然用C++,就接受现实吧。其实,显示远比一般人想象的要复杂。

以上情况还是函数没有参数的时候,当函数有参数的时候,新的问题又来了。

看看python中这样一个简单的功能:

def add(a,b):
    return a + b

l = [1,2,3,4,5]
for i in l:
    print add(i,1)

 

无非就是在每个输出的函数中调用一个函数,没有任何值的一提的地方,是个人就能看懂。

C++需要实现成下面这个样子:

#include <list>

#include <iostream>

#include <algorithm>

#include <functional>

using namespace std;

 

template <typename T>

class Add : public binary_function<T, T, void>

{

public:

    void operator()(const T& ai, const T& aj) const

    {

       cout <<(ai + aj) <<endl;

    }

 

};

 

int main()

{

    int a[5] = {1,2,3,4,5};

    list<int> l(a, a+5);

 

    for_each(l.begin(), l.end(), bind2nd(Add<int>(), 1));

 

    system("PAUSE");

}

 

到这一步,我希望大部分的C++程序员还能看懂什么意思及其实现的机制。。。。但是仅仅是我的希望吧,甚至我怀疑,这样的实现放在工作中,总监和老总是不是会将我批的体无完肤,的确,为了省略一个循环值得这样做吗?实在不值得,但是C++提供给你的机制就是这样。Add这样的函数对象构造复杂,还得利用trail机制(从binary_function类继承过来),然后再利用函数适配器bind2nd/bind1st,这样的东西似乎需要语言专家来解释,我是解释不清楚了,再加上更加复杂的函数连标准库中的bind都肯定不够用,还只能用boost::bind库,去试试吧,然后会发现一般的函数指来指去(特别是类成员)用的太复杂了,还是用boost::funciton吧。。。。。似乎永无止境。但是有了for each语法,那么什么复杂度都没有。。。。。还想自虐吗?算了吧,我基本上已经放弃了。不给糖吃,也放不着自己开工厂制作。。。。

另外,对于能够用boost的兄弟们,糖是有的吃的。boost:: foreach库即是如此。

 

下面是boost:: foreach的例子

#include <list>

#include <iostream>

#include <boost/foreach.hpp>

using namespace std;

 

 

int main()

{

    int a[5] = {1,2,3,4,5};

    list<int> l(a, a+5);

 

    BOOST_FOREACH( int i, l)

    {

       cout <<i <<endl;

    }

 

    BOOST_FOREACH( int i, l)

    {

       cout <<i+1 <<endl;

    }

 

    system("PAUSE");

}

 

就算仅仅这一个例子。。。。永远不要怪库开发者(比如boost,ace,loki)将C++语言弄得多么扭曲,他们也是出于无奈。。。。别去看实现,先只管用吧。

对于不能用boost的我。。。。只能看有没有办法偷偷的将/cli编译选项打开了。。。^^

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

Observer模式的升级版,Event通知实现

Observer模式的升级版,Event通知实现

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

本来这段程序是为mmgwg小组程序做示范写的,但是想想最近也很少有时间在家这样写程序了。。。。还是放上来吧,应该对有类似需求的兄弟们也有一些示范作用,对于程序的类层次结构就没有更多的深究了,仅仅是表达大概的意思,并不是希望大家都像我一样所有的实现都跑到父类中去。切记。

要说明的是,原始的Observer模式仅仅说明了一种思想,但是使用价值并不高,因为很简单的道理,作为一个系统基础的行为模式,其仅仅支持一种通知方式,并且是遍历通过。。。(这也可以看做模式的说明仅仅是实现了一种很通用的情况,并不是最好的实现),我们使用的也可以看作是Observer模式的延伸,也可以看做Observer模式的另一种实现,是所谓的 Event通知方式。在实际代码中使用非常多。在我们公司代码中似乎不论是服务器还是客户端代码都需要用到。服务器代码中甚至为了实现策划编辑数据另外做了一套(也就是并行的两套)。

具体的例子我打包放到

http://groups.google.com/group/jiutianfile/

中了,文件名为Event Drive sample code.rar

程序没有经过调试,仅仅是编译通过,请仅仅作为示范使用。其中唯一比较难掌握的可能就是类成员函数的指针了,但是难的主要是语法,真正的使用上没有太多问题。

Observer.h

 

#ifndef __OBSERVER_H__

#define __OBSERVER_H__

#include "BaseDefines.h"

 

class CSubject;

class CObserver

{

typedef LRESULT (CObserver:: *EventFunc)(LPARAM , WPARAM , WPARAMEX );

 

public:

    CObserver(CSubject* apSubject);

    virtual ~CObserver(void);

 

    bool InitEvent();

 

    // 此接口相当于原设计模式中的Update

    LRESULT OnEvent(EventID_t aiEventID, LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx);

 

    typedef map<EventID_t, EventFunc> EventMap_t;

    typedef EventMap_t::iterator EventIter_t;

    EventMap_t moEvents;

 

 

    // 以下仅为示例,即是各个事件的相应

    LRESULT OnEvent1( LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx);

    LRESULT OnEvent2( LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx);

    LRESULT OnEvent3( LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx);

 

    CSubject *mpSubject;

};

 

#endif

 

Observer.cpp

#include "StdAfx.h"

#include "Observer.h"

#include "Subject.h"

 

CObserver::CObserver(CSubject* apSubject):mpSubject(apSubject)

{

    InitEvent();

}

 

CObserver::~CObserver(void)

{

}

 

// 此实现中通过aiEventID分发事件,具体方式类似于MFC的消息映射

LRESULT CObserver::OnEvent( EventID_t aiEventID, LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx )

{

    EventIter_t lit = moEvents.find(aiEventID);

    if(lit == moEvents.end())

    {

       // 表示Observer实际没有订阅此事件。

       return -1;

    }

 

    // 假如有订阅,则调用相应的响应函数

    return ( this->*(lit->second))(aLParam, aWParam, aWParamEx);

}

 

bool CObserver::InitEvent()

{

    moEvents.insert(make_pair(1, &CObserver::OnEvent1));

    moEvents.insert(make_pair(2, &CObserver::OnEvent2));

    moEvents.insert(make_pair(3, &CObserver::OnEvent3));

 

    return true;

}

 

LRESULT CObserver::OnEvent1( LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx )

{

 

    return 1;

}

 

LRESULT CObserver::OnEvent2( LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx )

{

 

    return 1;

}

 

LRESULT CObserver::OnEvent3( LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx )

{

 

    return 1;

}

 

Subject.h

#ifndef __SUBJECT_H__

#define __SUBJECT_H__

 

#include "BaseDefines.h"

#include <map>

#include <list>

using namespace std;

 

class CObserver;

class CSubject

{

public:

    CSubject(void);

    virtual ~CSubject(void);

 

 

    bool Attach( EventID_t aiEventID, CObserver *apObs);

    bool Detach( EventID_t aiEventID, CObserver *apObs);

 

    // 类似原Observer模式中的Notice消息

    LRESULT SendEvent(EventID_t aiEventID, LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx );

 

    typedef list<CObserver*> ObsList_t;

    typedef ObsList_t::iterator ObsIter_t;

 

    typedef map<EventID_t, ObsList_t> EventObsMap_t;

    typedef EventObsMap_t::iterator EventObsIter_t;

    EventObsMap_t moEventObsMap;

};

 

#endif

 

Subject.cpp

#include "StdAfx.h"

#include "Subject.h"

#include "Observer.h"

#include <boost/bind.hpp>

 

CSubject::CSubject(void)

{

}

 

CSubject::~CSubject(void)

{

}

 

 

bool CSubject::Attach( EventID_t aiEventID, CObserver *apObs )

{

    EventObsIter_t lit = moEventObsMap.find(aiEventID);

    if(lit != moEventObsMap.end())

    {// 已经有相关的事件响应Obs列表

       ObsList_t& lObsList = lit->second;

       ObsIter_t litList = find(lObsList.begin(), lObsList.end(), apObs);

       if(litList != lObsList.end())

       {// 重复Attach

           return false;

       }

 

       lObsList.push_back(apObs);

       return true;

    }

 

    // 实际上先添加listmap然后再添加bos效率会略高。

    ObsList_t lObsList;

    lObsList.push_back(apObs);

 

    // insert必成功

#ifdef _DEBUG

    pair<EventObsIter_t, bool> lpairResult = moEventObsMap.insert(make_pair(aiEventID, lObsList));

    ASSERT(lpairResult.second);

#else

    moEventObsMap.insert(make_pair(aiEventID, lObsList));

#endif

 

    return true;

}

 

bool CSubject::Detach( EventID_t aiEventID, CObserver* apObs )

{

    EventObsIter_t lit = moEventObsMap.find(aiEventID);

    if(lit == moEventObsMap.end())

    {// 根本没有订阅此消息

       return false;

    }

 

    ObsList_t& lObsList = lit->second;

    ObsIter_t litList = find(lObsList.begin(), lObsList.end(), apObs);

    if(litList == lObsList.end())

    {// 根本没有订阅此消息

       return false;

    }

 

    // 这里当list为空的时候也不删除map中的对应item,添加的时候速度更快,也没有删除的时间,消耗多一点内存,但是减少内存碎片

    lObsList.erase(litList);

 

    return true;

}

 

LRESULT CSubject::SendEvent( EventID_t aiEventID, LPARAM aLParam, WPARAM aWParam, WPARAMEX aWParamEx )

{

    EventObsIter_t lit = moEventObsMap.find(aiEventID);

    if(lit == moEventObsMap.end())

    {// 根本没有Observer订阅此消息

       return 0;

    }

 

    ObsList_t& lObsList = lit->second;

    for(ObsIter_t litList = lObsList.begin(); litList != lObsList.end(); ++litList)

    {

       (*litList)->OnEvent(aiEventID, aLParam, aWParam, aWParamEx);

    }

 

    return 1;

}

 

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

序列化支持(4)—Boost的序列化库的强大之处

序列化支持(4)—Boost的序列化库的强大之处

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

1.      非介入式版本

感觉实际使用中我还没有碰到过,既然需要完全的public才能还原数据,那么介入不介入好像影响也不大了,除非碰到一个东西是别的公司写的,不让改,我是还没有碰到这样的情况。

 

从这里开始见识Boost序列化库的强大。。。。。。。。

 

2.      指针的序列化:

下面的例子为文档中例子的简化,并添加必要部分以方便运行及演示。

// BoostLearn.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

 

#include <fstream>

 

// 包含以简单文本格式实现存档的头文件

#include <boost/archive/text_oarchive.hpp>

#include <boost/archive/text_iarchive.hpp>

 

/////////////////////////////////////////////////////////////

// gps 座标

//

// 举例说明简单类型的序列化

//

class gps_position

{

private:

    friend class boost::serialization::access;

    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive

    // 是一个输入存档,则操作符& 被定义为>>.

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & degrees;

       ar & minutes;

       ar & seconds;

    }

    int degrees;

    int minutes;

    float seconds;

public:

    gps_position()

    {

       degrees = 0;

       minutes = 0;

       seconds = 0.0;

    };

    gps_position(int d, int m, float s) :

    degrees(d), minutes(m), seconds(s)

    {}

};

 

class bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & latitude;

       ar & longitude;

    }

 

    gps_position latitude;

    gps_position longitude;

public:

    bus_stop(){ }

    bus_stop(const gps_position & lat_, const gps_position & long_) :

        latitude(lat_), longitude(long_){ }

        

    virtual ~bus_stop(){ }

};

 

class bus_stop_corner : public bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       // 序列化基类信息

       ar & boost::serialization::base_object<bus_stop>(*this);

       ar & street1;

       ar & street2;

    }

    std::string street1;

    std::string street2;

 

public:

    bus_stop_corner(){}

    bus_stop_corner(const gps_position & lat_, const gps_position & long_,

       const std::string & s1_, const std::string & s2_

       ) :

    bus_stop(lat_, long_), street1(s1_), street2(s2_)

    {}

 

    virtual std::string description() const

    {

       return street1 + " and " + street2;

    }

};

 

 

class bus_route

{

    friend class boost::serialization::access;

    bus_stop_corner * stops[2];

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       int i;

       for(i = 0; i < 2; ++i)

           ar & stops[i];

    }

public:

    bus_route(bus_stop_corner *apStop1, bus_stop_corner *apStop2)

    {

       stops[0] = apStop1;

       stops[1] = apStop2;

    }

    bus_route(){}

};

 

 

 

int main() {

    // 创建并打开一个输出用的字符存档

    std::ofstream ofs("bus_route");

 

    // 创建类实例

    const gps_position latitude(1, 2, 3.3f);

    const gps_position longitude(4, 5, 6.6f);

 

    bus_stop_corner *lpStop1 = new bus_stop_corner(latitude, longitude, "corn1", "corn2");

    bus_stop_corner *lpStop2 = new bus_stop_corner(latitude, longitude, "corn3", "corn4");

 

    bus_route route(lpStop1, lpStop2);

 

    // 保存数据到存档

    {

       boost::archive::text_oarchive oa(ofs);

       // 将类实例写出到存档

       oa << route;

       // 在调用析构函数时将关闭存档和流

    }

 

    // ... 晚些时候,将类实例恢复到原来的状态

    bus_route new_route;

    {

       // 创建并打开一个输入用的存档

       std::ifstream ifs("bus_route", std::ios::binary);

       boost::archive::text_iarchive ia(ifs);

       // 从存档中读取类的状态

       ia >> new_route;

       // 在调用析构函数时将关闭存档和流

    }

 

    delete lpStop1;

    delete lpStop2;

    return 0;

}

 

 

这里的强大之处在于指针反序列化的时候自动的分配了内存,这样简化了很多的操作,当然,这样就会存在文档中提出的内存泄露的问题,在此例中的确存在,反序列化时分配了内存但是却没有合理的地方去释放,由外部去释放感觉并不是太妥当,boost文档中的建议是使用智能指针,比如share_ptr。这个例子我们放到最后,先看看利用普通类的一条确保内存分配并不泄露的原则,哪里分配的哪里释放,对象自己管理自己的内存。

见下例:(此例为文档中没有的)

 

// BoostLearn.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

 

#include <fstream>

 

// 包含以简单文本格式实现存档的头文件

#include <boost/archive/text_oarchive.hpp>

#include <boost/archive/text_iarchive.hpp>

 

/////////////////////////////////////////////////////////////

// gps 座标

//

// 举例说明简单类型的序列化

//

class gps_position

{

private:

    friend class boost::serialization::access;

    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive

    // 是一个输入存档,则操作符& 被定义为>>.

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & degrees;

       ar & minutes;

       ar & seconds;

    }

    int degrees;

    int minutes;

    float seconds;

public:

    gps_position()

    {

       degrees = 0;

       minutes = 0;

       seconds = 0.0;

    };

    gps_position(int d, int m, float s) :

    degrees(d), minutes(m), seconds(s)

    {}

};

 

class bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & latitude;

       ar & longitude;

    }

 

    gps_position latitude;

    gps_position longitude;

public:

    bus_stop(){ }

    bus_stop(const gps_position & lat_, const gps_position & long_) :

        latitude(lat_), longitude(long_){ }

        

    virtual ~bus_stop(){ }

};

 

class bus_stop_corner : public bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       // 序列化基类信息

       ar & boost::serialization::base_object<bus_stop>(*this);

       ar & street1;

       ar & street2;

    }

    std::string street1;

    std::string street2;

 

public:

    bus_stop_corner(){}

    bus_stop_corner(const gps_position & lat_, const gps_position & long_,

       const std::string & s1_, const std::string & s2_

       ) :

    bus_stop(lat_, long_), street1(s1_), street2(s2_)

    {}

 

    virtual std::string description() const

    {

       return street1 + " and " + street2;

    }

};

 

 

class bus_route

{

    friend class boost::serialization::access;

 

    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了

    bus_stop_corner * stops[2];

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       for(int i = 0; i < 2; ++i)

           ar & stops[i];

    }

public:

    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2)

    {

       stops[0] = new bus_stop_corner(aoStop1);

       stops[1] = new bus_stop_corner(aoStop2);

    }

 

    bus_route()

    {

       stops[0] = new bus_stop_corner;

       stops[1] = new bus_stop_corner;

    }

 

    ~bus_route()

    {

       for(int i = 0; i < 2; ++i)

       {

           delete stops[i];

       }

    }

};

 

 

 

int main() {

    // 创建并打开一个输出用的字符存档

    std::ofstream ofs("bus_route");

 

    // 创建类实例

    const gps_position latitude(1, 2, 3.3f);

    const gps_position longitude(4, 5, 6.6f);

 

    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");

    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");

 

    bus_route route(loStop1, loStop2);

 

    // 保存数据到存档

    {

       boost::archive::text_oarchive oa(ofs);

       // 将类实例写出到存档

       oa << route;

       // 在调用析构函数时将关闭存档和流

    }

 

    // ... 晚些时候,将类实例恢复到原来的状态

    bus_route new_route;

    {

       // 创建并打开一个输入用的存档

       std::ifstream ifs("bus_route", std::ios::binary);

       boost::archive::text_iarchive ia(ifs);

       // 从存档中读取类的状态

       ia >> new_route;

       // 在调用析构函数时将关闭存档和流

    }

    return 0;

}

 

 

其实在一般情况下,只需要遵循了上述的原则,内存泄露问题一般不会存在,但是这里有个疑问就是,当指针分配了内存,boost序列化的时候是不是还是傻傻的去重新分配一次内存,然后导致第一次分配的内存没有正常释放,导致内存泄露呢?我们来检验一样。

new_route调用默认构造函数分配内存时,数组中指针的地址如下:

[0] = 0x003b7090 {street1="" street2="" }

[1] = 0x003bafb0 {street1="" street2="" }

 

反序列化后:

[0] = 0x003b9150 {street1="corn1" street2="corn2" }

[1] = 0x003b9268 {street1="corn3" street2="corn4" }

经证实。。。。。boost在指针已经分配过内存的情况下仍然重新为指针分配了一次内存,这一点够傻的,那么,这样傻的行为有没有一点保护呢?比如首先判断一下指针是否为NULL,然后先deletenew呢?虽然这样的操作好像更傻。。。当时总好过与内存泄露,验证一下的方法很简单,在ia >> new_route;一句执行前在bus_stop_corner的析构函数上加断电,假如boost调用了delete,其析构一定会发生,事实是残酷的。。。。假如事先为指针分配了内存,那么必然发生内存的泄露。。。。boost根本不管指针是否已经分配过内存,直接忽略,并重新分配内存。

 

其实,一般而言,需要反序列化的时候,提供一个空的对象也是比较合理的,毕竟这是一个还原对象的过程,所以程序改成下面例子这样就可以在不使用智能指针的时候避免内存泄露了。

 

// BoostLearn.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

 

#include <fstream>

 

// 包含以简单文本格式实现存档的头文件

#include <boost/archive/text_oarchive.hpp>

#include <boost/archive/text_iarchive.hpp>

#include <cstdlib>

using namespace std;

 

/////////////////////////////////////////////////////////////

// gps 座标

//

// 举例说明简单类型的序列化

//

class gps_position

{

private:

    friend class boost::serialization::access;

    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive

    // 是一个输入存档,则操作符& 被定义为>>.

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & degrees;

       ar & minutes;

       ar & seconds;

    }

    int degrees;

    int minutes;

    float seconds;

public:

    gps_position()

    {

       degrees = 0;

       minutes = 0;

       seconds = 0.0;

    };

    gps_position(int d, int m, float s) :

    degrees(d), minutes(m), seconds(s)

    {}

};

 

class bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & latitude;

       ar & longitude;

    }

 

    gps_position latitude;

    gps_position longitude;

public:

    bus_stop(){ }

    bus_stop(const gps_position & lat_, const gps_position & long_) :

        latitude(lat_), longitude(long_){ }

        

    virtual ~bus_stop(){ }

};

 

class bus_stop_corner : public bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       // 序列化基类信息

       ar & boost::serialization::base_object<bus_stop>(*this);

       ar & street1;

       ar & street2;

    }

    std::string street1;

    std::string street2;

 

public:

    bus_stop_corner(){}

    bus_stop_corner(const gps_position & lat_, const gps_position & long_,

       const std::string & s1_, const std::string & s2_

       ) :

    bus_stop(lat_, long_), street1(s1_), street2(s2_)

    {}

 

    virtual std::string description() const

    {

       return street1 + " and " + street2;

    }

 

    ~bus_stop_corner()

    {

    }

};

 

 

class bus_route

{

    friend class boost::serialization::access;

 

    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了

    bus_stop_corner * stops[2];

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       for(int i = 0; i < 2; ++i)

           ar & stops[i];

    }

public:

    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2)

    {

       stops[0] = new bus_stop_corner(aoStop1);

       stops[1] = new bus_stop_corner(aoStop2);

    }

 

    bus_route()

    {

       stops[0] = NULL;

       stops[1] = NULL;

    }

 

    ~bus_route()

    {

       for(int i = 0; i < 2; ++i)

       {

           if(stops[i] != NULL)

           {

              delete stops[i];

           }

       }

    }

};

 

 

 

int main() {

    // 创建并打开一个输出用的字符存档

    std::ofstream ofs("bus_route");

 

    // 创建类实例

    const gps_position latitude(1, 2, 3.3f);

    const gps_position longitude(4, 5, 6.6f);

 

    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");

    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");

 

    bus_route route(loStop1, loStop2);

 

    // 保存数据到存档

    {

       boost::archive::text_oarchive oa(ofs);

       // 将类实例写出到存档

       oa << route;

       // 在调用析构函数时将关闭存档和流

    }

 

    // ... 晚些时候,将类实例恢复到原来的状态

    bus_route new_route;

    {

       // 创建并打开一个输入用的存档

       std::ifstream ifs("bus_route", std::ios::binary);

       boost::archive::text_iarchive ia(ifs);

       // 从存档中读取类的状态

       ia >> new_route;

       // 在调用析构函数时将关闭存档和流

    }

    return 0;

}

 

 

这里的bus_route类有指针和内存分配但是没有合理的拷贝构造函数和operator=重载,仅仅只是作为演示使用,实际中这里几乎是必须的,即使不需要使用到复制也应该将此两个函数放入private中以表示禁止复制,以防误用。(比如stdI/O stream类实现即是如此)

 

改成上述例子中的形式后,需要注意的是反序列化前一定要是一个空对象,假如以前有分配内存的话需要提前释放到,还好这些都可以很简单的由对象本身所保证。这一点可能的错误应用应该算是Boost为了易用性而导致的。。。。基本掌握了还算能接受,起码对于指针的序列化还是简单了很多,仅仅是需要多注意一下传入的必须是空的指针就行。

 

Boost作为准标准库,虽然有的时候显得有点庞大,但是STL的搭配,和众多新Boost特性的搭配是非常的默契(不知道这个词是否恰当)。从上面的序列化就可以看出来,序列化是完全融入原有的C++ stream体系的,这点我们公司的序列化根本没有办法比。谈到这点就是想说,其实包括auto_ptr甚至shared_ptr在内的智能指针,包括vector,mapSTL容器,甚至连array在内。

 

这里是使用智能指针的两个例子,但是boostserialize库如此偏心。。。。。share_ptr是内嵌在库里面的,而C++标准库的auto_ptr竟然没有内嵌在库里面,仅仅是在demo中给出实现。。。。。。也就是说,明明实现了,就是不想放到库里面去。。。。。如此不推荐使用auto_ptr的做法,完全见证了我当时强烈感叹的auto_ptr的异类。。。。我就不知道他是怎么混进标准库的。

 

以下是智能指针的使用示例代码:

// BoostLearn.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

 

#include <fstream>

 

// 包含以简单文本格式实现存档的头文件

#include <boost/archive/text_oarchive.hpp>

#include <boost/archive/text_iarchive.hpp>

#include <cstdlib>

#include <boost/tr1/memory.hpp>

#include <vector>

#include <map>

#include <boost/tr1/unordered_map.hpp>

#include <memory>

#include <boost/serialization/shared_ptr.hpp>

using namespace std;

using namespace boost;

using namespace boost::serialization;

 

#include <boost/serialization/split_free.hpp>

 

namespace boost {

    namespace serialization {

 

       /////////////////////////////////////////////////////////////

       // implement serialization for auto_ptr<T>

       // note: this must be added to the boost namespace in order to

       // be called by the library

       template<class Archive, class T>

       inline void save(

           Archive & ar,

           const std::auto_ptr<T> &t,

           const unsigned int file_version

           ){

              // only the raw pointer has to be saved

              // the ref count is rebuilt automatically on load

              const T * const tx = t.get();

              ar << tx;

       }

 

       template<class Archive, class T>

       inline void load(

           Archive & ar,

           std::auto_ptr<T> &t,

           const unsigned int file_version

           ){

              T *pTarget;

              ar >> pTarget;

              // note that the reset automagically maintains the reference count

#if BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1)

              t.release();

              t = std::auto_ptr<T>(pTarget);

#else

              t.reset(pTarget);

#endif

       }

 

       // split non-intrusive serialization function member into separate

       // non intrusive save/load member functions

       template<class Archive, class T>

       inline void serialize(

           Archive & ar,

           std::auto_ptr<T> &t,

           const unsigned int file_version

           ){

               boost::serialization::split_free(ar, t, file_version);

       }

 

    } // namespace serialization

} // namespace boost

 

 

/////////////////////////////////////////////////////////////

// gps 座标

//

// 举例说明简单类型的序列化

//

class gps_position

{

private:

    friend class boost::serialization::access;

    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive

    // 是一个输入存档,则操作符& 被定义为>>.

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & degrees;

       ar & minutes;

       ar & seconds;

    }

    int degrees;

    int minutes;

    float seconds;

public:

    gps_position()

    {

       degrees = 0;

       minutes = 0;

       seconds = 0.0;

    };

    gps_position(int d, int m, float s) :

    degrees(d), minutes(m), seconds(s)

    {}

};

 

class bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & latitude;

       ar & longitude;

    }

 

    gps_position latitude;

    gps_position longitude;

public:

    bus_stop(){ }

    bus_stop(const gps_position & lat_, const gps_position & long_) :

        latitude(lat_), longitude(long_){ }

        

    virtual ~bus_stop(){ }

};

 

class bus_stop_corner : public bus_stop

{

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       // 序列化基类信息

       ar & boost::serialization::base_object<bus_stop>(*this);

       ar & street1;

       ar & street2;

    }

    std::string street1;

    std::string street2;

 

public:

    bus_stop_corner(){}

    bus_stop_corner(const gps_position & lat_, const gps_position & long_,

       const std::string & s1_, const std::string & s2_

       ) :

    bus_stop(lat_, long_), street1(s1_), street2(s2_)

    {}

 

    virtual std::string description() const

    {

       return street1 + " and " + street2;

    }

 

    ~bus_stop_corner()

    {

    }

};

 

 

class bus_route

{

    friend class boost::serialization::access;

 

    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了

    shared_ptr<bus_stop_corner> msptrBusStop;

    auto_ptr<bus_stop_corner> maptrBusStop;

 

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & msptrBusStop;

       ar & maptrBusStop;

    }

public:

    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2):

      msptrBusStop(new bus_stop_corner(aoStop1)),

       maptrBusStop(new bus_stop_corner(aoStop2))

    {

    }

 

    bus_route()

    {

 

    }

 

    ~bus_route()

    {

    }

};

 

 

 

int main() {

    // 创建并打开一个输出用的字符存档

    std::ofstream ofs("bus_route");

 

    // 创建类实例

    const gps_position latitude(1, 2, 3.3f);

    const gps_position longitude(4, 5, 6.6f);

 

    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");

    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");

 

    bus_route route(loStop1, loStop2);

 

    // 保存数据到存档

    {

       boost::archive::text_oarchive oa(ofs);

       // 将类实例写出到存档

       oa << route;

       // 在调用析构函数时将关闭存档和流

    }

 

    // ... 晚些时候,将类实例恢复到原来的状态

    bus_route new_route;

    {

       // 创建并打开一个输入用的存档

       std::ifstream ifs("bus_route", std::ios::binary);

       boost::archive::text_iarchive ia(ifs);

       // 从存档中读取类的状态

       ia >> new_route;

       // 在调用析构函数时将关闭存档和流

    }

    return 0;

}

 

至于剩下的STL容器之类的,由于和普通的成员变量都看不出区别了,我弄个简单的示例说明一下就好了,按原文档的话来说是:

The serialization library contains code for serialization of all STL classes.

还不够吗?

从上面的例子上已经可以看出std::string肯定没有问题了,我只想知道一个东西,tr1unordered_map实现了没有。但是非常遗憾。。。。。呵呵,没有实现,不过vectorlist的实现自然是没有问题,一下给出例子,这里需要说明的是,对于STL来说,可能是考虑到每个实现都比较大,所以在使用了相应的容器后,需要包含序列化相应的头文件,比如vector就是boost/serialization/vector.hpp,依次类推,这点share_ptr的示例其实就已经使用了,但是没有特别说明。

 

// BoostLearn.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

 

#include <fstream>

 

// 包含以简单文本格式实现存档的头文件

#include <boost/archive/text_oarchive.hpp>

#include <boost/archive/text_iarchive.hpp>

#include <cstdlib>

#include <boost/tr1/memory.hpp>

#include <vector>

#include <map>

#include <memory>

#include <boost/serialization/vector.hpp>

using namespace std;

using namespace boost;

using namespace boost::serialization;

 

class CSerializeAble

{

public:

    std::vector<int> miVec;

 

    friend class boost::serialization::access;

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & miVec;

    }

};

int main() {

    // 创建并打开一个输出用的字符存档

    std::ofstream ofs("contains");

 

    CSerializeAble loSA;

    loSA.miVec.push_back(1);

    loSA.miVec.push_back(2);

    loSA.miVec.push_back(3);

 

    // 保存数据到存档

    {

       boost::archive::text_oarchive oa(ofs);

       // 将类实例写出到存档

       oa << loSA;

       // 在调用析构函数时将关闭存档和流

    }

 

    // ... 晚些时候,将类实例恢复到原来的状态

    CSerializeAble lonewSA;

    {

       // 创建并打开一个输入用的存档

       std::ifstream ifs("contains", std::ios::binary);

       boost::archive::text_iarchive ia(ifs);

       // 从存档中读取类的状态

       ia >> lonewSA;

       // 在调用析构函数时将关闭存档和流

    }

    return 0;

}

 

 

 

到了这里,已经可以看到Boost::Serialize的强大了,这里还想说明的是,虽然文档中有std::list<bus_stop *> stops;这样的例子,但是实际上我这样做却无法编译通过。。。。。。。。这点很奇怪
然后,Boost::Serialize由于是完全融入C++IOStream系统的,所以,只要你实现你自己的Stream,比如文档中提到的XML系统,你就可以实现任意的方式保存你序列化的数据。这样的开放性也是值得一提的,光是这一点都不是一般的非“准标准库可以比拟的。。。。。。。。序列化大概就讲这么多吧。。。。序列化在公司只用了半天去了解,但是自己这段时间工作太忙,结果学习Boost的序列化库的使用,还不是看其实现,就用了这么久,真是无奈。。。。

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

Debug思考模式(1)—关注更改

Debug思考模式(1)—关注更改

程序员就像诗人,他的工作几乎全是纯思考弗里德里克·布鲁克斯

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 

假如设计有模式,分析有模式,那么,程序员最希望有的自然是Debug的模式了。。。真希望有人已经总结出来了,以节省我们曾经浪费的大量时间。。。。可惜。。呵呵,好像没有看到,我碰到一些问题,顺便总结一下方法吧,当方法重复的被使用,那么也就成为一种Debug思考模式了。

 

前段时间为公司的TCP网络模块添加加密的功能,当时用的加密算法是从网上找的。。。为了安全,就不透露具体的算法了,算法的加密解密函数都写的很简单,就两个指针参数,由于仅仅是简单的算法实现,主要用于描述算法,没有配套的使用文档,也没有示例代码,一番折腾,调试很久,总算明白了2个参数的作用,不就一个传入需要加密解密的数值,一个传入自定义的key嘛。Debug通过后,直接编译了release版本给测试组测试,发现完全没有发挥作用。相当的郁闷,回来再调,还是正确的,但是编译成release版本后的确不行了。一般情况下碰到类似问题,不知道怎么的,都是第一反应没有初始化,查找外围代码老半天,并没有发现问题,由于Release下的调试功能比较弱,所以在算法加密解密函数调用前将内存中的值用日志的形式写了下来,发现加密本身就与debug下不一致,确定了问题改变的地点,肯定是加密算法的问题。仔细在release下调试加密算法,还好学会汇编,找到具体地点时release用汇编看就不那么吃力了,原来此算法的传入加密数值是64bit的,但是key却是用了128bit-_-!在没有任何说明注释的情况下,仅仅以两个指针作为参数传入,不自己调试根本没有办法发现。。。。。唉。。。碰巧Debug能对,在于MS为所有的Debug未初始化变量赋值0xcc。。。也就是在Debug下,我的key指针传入实际相当于是以我自己的key加上两个连续的0xcc,无论加密解密的key都还是一样的,所以一切正常,当release时,越界访问的值属于随机状态,key不一致了,加密解密自然也就不肯能对了。。。。。

首先还是的说一点,好的接口是容易用对的,坏的接口设计(比如这种)是容易用错的。这是《Effective C++》一书中讲过的,但是看来还是很多人没有真正理解,我就是受害者之一。。。。到了这里,可能会有人批评我,说假如我具体的了解了算法和看清楚了算法的实现就不会出这样的错误了,但是我想说的是,好的接口给人使用,就是让人可以不了解实现的,甚至接口和实现都可以分离,使用接口的人根本就需不要关心实现,这样才能更加关注于自己需要关注的东西。何况,工作嘛,哪能真的用什么就理解透了才去做啊。。。。。。

我这里想说的是,我犯的最大的错误在于出现问题时,没有很快的定位到错误所在的地方,事实上TCP网络模块公司一直在使用,我仅仅添加了加密模块,然后出现问题,那么首先可以想到的自然是加密模块的问题,但是由于我平时对公司的TCP网络模块没有太深入的了解,仅仅是平时调试的时候偶尔跟进去看看,所以对其还有怀疑,然后出现错误,特别是这种debug没有错误,release错误时,一下子没有了方向,开始漫无目的的乱怀疑,其实根本没有必要,一开始就应该定位到错误所在。

这就是总监最后批评我没有使用排除法的地方,此处我应该将原来正确的代码排除掉。

这是我想提出的一种Debug思考模式:

假如在更改此模块之前其他模块都能正常工作,首先关注于这次更改的模块。

 

这种Debug模式其实真的能深入到思想中,其实作用很大,但是需要注意一些特殊的例子。比如有一次,我为公司的世界地图服务器添加GM模块,此时由于公司内网测试使用的GM指令与此非常类似,于是我主要通过此GM指令系统来搭建新的模块,一切都非常顺利,由于新模块建立在旧模块上,代码的编写速度也是非常快,但是到了最后,又出现了问题,程序一运行就崩溃。。。。这时又另外一个问题,加重了问题的严重性,我从程序一开始的地方到崩溃的地方下了很多断点,但是断点竟然都断不上,一般出现这种情况,属于库和头文件不统一造成,但是此次的调试仅仅在本模块中,没有进入其他的库,所以让人很郁闷,后来发现的问题在于原有的GM指令根本不需要了解是谁在执行,所以没有使用IPlayer的指针,但是我由于需要详细记录GM的每个动作,所以必须使用到此指针,使用的时候由于是复制代码,所以完全复制原有代码过来,也没有思考,原来的使用就是通过OnMessge的参数WPARAM强转过来的,但是原来并没有使用,我在后面将其传入另一个函数并使用了此指针,导致程序崩溃,按总监的话来说,由于IPlayer属于多重继承而来,不能再像普通类型一样用强转了,以前的代码是老代码,所以使用的并不正确,但是由于没有使用,所以没有问题。也就是说,我栽倒在了一次原来的老代码强转错误上。这里,原有模块同样没有出现问题,而我的程序出现了。区别在于原有代码强转了指针却没有使用,我使用了其强转,接下来还是用了指针。

这里需要提出的是:

原有程序没有出现问题,不代表代码没有问题,关注于更改,就要关注每个细微的更改。

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

Python,Spam的有趣由来

 看到Dreaming in code一书中提到这个词,才想起gmail中的字段的确切含义-_-!顺便在网上查了一下:

官方版本说,它是“Specially Processed Assorted Meat”特殊加工过的混和肉。这种SPAM肉有段时间非常普及,到了无处不在,令人讨厌的程度.

 

垃圾邮件之所以取名spam,在于互联网那一批先驱为其取名的时候,有一部很流行的剧集,名叫“Monty Python's Flying Circus”,剧集中有个小餐厅,他们的菜单上只有“鸡蛋,香肠,spam,spam,spam,and spam”。。。。。。。。。呵呵

 

看到剧集名,是不是很眼熟啊。。。。。。。的确,Python语言的由来并不是因为Guido van Rossum觉得大蟒蛇很厉害,而且因为他如此的喜欢此剧集。。。。。。。所以如此命名Python语言

阅读全文....

假如说Debug是减少Bug的过程,那么编程就是制造Bug的过程


假如说Debug是减少Bug的过程,那么编程就是制造Bug的过程

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

       有人说程序员工作中干的最多的事情不是编程,而是Debug,此言实在不虚。按我工作的经验来看,一般而言,设计占1/3,编码占1/3Debug/测试占1/3。但是实际上。。。由于工作中慢慢发现问题,除了专门的Debug调试及测试时间,在后期产品维护的时候也需要很多实现来Debug,在初期往往是Debug的定位容易,修改也容易,到了中期要定位一个Bug就很难了,但是改起来却非常容易,到了后期假如还有Bug,那么常常会发现。。。要改动的几乎就是架构。

       往往实际工作中的编程高手不是那种代码写的多么有技巧,架构多么完善灵活,程序效率多么高,真正让人佩服的就是一针见血,准确定位Bug的人。

       程序员也算是一个常常以工作经验来衡量水平的群体,并且此工作经验往往是以工作年份来计算的,做为刚工作的新人,也许偶尔看到公司的老员工写出来的代码会不屑,觉得自己也能写出来,甚至写的更好,偶尔看到公司的程序框架觉得设计太烂,与自己学习的设计模式经验相差太远,但是真正碰到Bug的时候却会发现差距。。。。新手最容易说的话就是这个程序不是我写的,所以改这个Bug我得先熟悉一下代码,而真正的高手,拿到代码碰到Bug基本就能猜到是什么问题。。。。

       今天碰到的事情让我对上述言论有了更加深刻的认识。

       公司的游戏专卖店系统首先出现了上架的物品加载不上的问题,修复后,出现了物品与账户不同步的问题。按道理有上架物品就应该有账户,没有一个上架物品时删除此物品。当出现有上架物品没有玩家账户的时候,我一筹莫展,仅仅是反驳总监提出的上架时物品没有添加的问题。然后找到代码,明明白白指出,当物品上架时,的确是添加了账户。但是总监发现的确是正确添加账户后,马上查询了删除账户的情况,发现原来是因为以前物品没有加载上来,我判断没有上架物品,将其账户删除了。

       这就是总监的Debug水平高于我的地方,尽管世界地图服务器的专卖店系统是我写的,但是,Bug确是他先定位到。一方面总监能够逆向的去思考Bug的起因,当账户和物品不统一的时候,添加账户也成功了,自然的就去看看删除账户的时候。并且还能够联想到刚才发生的物品加载错误的情况。这些真是经验,为了吃一堑,长一智,我决定都将其记录下来。

       今天的第一智自然就是逆向的思考Bug的起因,当账户不存在的时候,添加页正确了,那么是不是删除错了呢?

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

Bug实际不一定出现在看起来出现的地方


Bug实际不一定出现在看起来出现的地方

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

作为初学者,一般都是看到Bug在哪发生的就只看到哪的代码,然后反复的想为什么会出,加日志吗,在Bug出现的前后两句加上日志,然后苦苦分析而得不出原因。当一个断言出现的时候更加是这种方法最派上用场的时候,因为Bug出在哪一行更明显了啊。

事实上,Bug却常常是因为其他原因引起的。最近的一个情况就是,文件打包工具在打包游戏数据时,总是有一个目录不能打进包中。我首先的方式是在Bug前后两句加上了日志,发现递归遍历文件和目录的时候并没有遍历到漏掉的那个目录。我是百思不得其解啊,不就是CFind的使用吗?MFC的东西不至于出现这样的错误吧?然后此问题在我的机器上又没有办法重现,所以我没有办法实际的调试代码。假如还是靠这样的思路几乎没有办法发现Bug了。

最后我回过头去,将日志添加了几个作用域,发现外面的遍历是正确的,再反过来看代码,发现原来是打包工具在定制哪个文件是确定打到那个包中时用的是平面结构,而测试组总监打包的时候用的是老的脚本,新的游戏数据添加了一个目录,打包工具的脚本中并不存在,在打包工具脚本遍历那一层就已经将那个目录丢掉了,所以里层的循环自然没有此目录,但是外层却能发现。。。这就是问题所在,假如我能早一点回过头去一层一层的看循环的条件,那么我可能很快就发现Bug了。。。。这也算是我不成熟的一个地方。虽然此例中有打包工具不是我做的,不是太熟悉的原因,但是不属于自己需要改Bug的代码不就是新手经验不足水平不够最最劣质的理由吗?(参考前一篇)

另外,这里举另外的例子,以前做文件打包系统的时候老是打包到一半出现ESP错误。。。。。。狂折腾代码,到了晚上十点总监亲自出马,全取代码重编,问题解决。。。。。。原来是编写工具的兄弟没有完全同步文件打包系统的库,头文件和lib不统一导致的问题。现在我是有经验了,碰到类似的问题,第一反应就是库没有统一。

还有一个例子,也是文件打包系统的问题,当时公司的游戏新加了声音,出现的问题是一旦声音打到我的包中,游戏运行会出现各种各样的错误,地图加载失败啊啥的,将声音从包中剥离出来,一点问题都没有。刚开始总监和我都怀疑是文件打包系统在频繁小批量读取时出现Bug,结果不是,然后只好跟代码,发现Bug时出时不出,这时总监和我的反应又都是哪个地方内存出现越界,导致这样莫名其妙的问题。但是最后狂看代码,折腾到十一点,突然总监一拍脑袋,不是吧,你的文件系统支不支持多线程啊?然后翻了翻声音系统的文档,明确说明,读取数据时需要线程安全。我彻底崩溃。。。。由于那个是我工作以来的第二个任务,我只需要关注,水平也就只够光注自己的的工作,根本就不知道声音那边是独立线程的。。。。一般而言,我们客户端是单线程的。。。。没有明确的要求,我怎么可能将文件系统做成线程安全啊?。。。。

第二天将文件系统做成线程安全以后。。。问题解决。。。这属于我最最刻骨铭心的Debug经历之一,还是那句话:Bug实际不一定出现在看起来出现的地方。---题外话:由于文件系统中为了统计数据等多种功能,太多函数带有状态和使用了类成员(特别是list),所以改成线程安全后效率惨不忍睹,而又没有办法将声音的线程取消,至今我们公司的游戏,声音还是没有打入我写的文件打包格式中,独立于打包系统之外。。。。。

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

序列化支持(3)—Boost的序列化库的使用

序列化支持(3)—Boost的序列化库的使用

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

       本来来说,Boost的文档属于开源库中最最详细的一列了,基本上跟着文档走就都能学会了,但是对于初学者来说可能有的地方太过简略,当然,对于熟悉boost的人来说那叫主题吐出,一针见血。我这里主要摘文档中的例子来讲讲,偶尔发表一下自己的见解,有的地方也跟进实现去看看。毕竟原有的例子仅仅是很简单的。这里自然还是推荐任何学习者都像我一样,调试其中的每一个例子,而不仅仅是看看而已。

       关于Boost的编译和配置假如觉的麻烦,可以下一个自动下载安装的程序来完成,在windows下,从1.35开始我就一直使用此自动安装程序,安装和卸载非常方便,接下来需要做的就仅仅是简单的添加好工作路径就行了。

以下例子如无特别说明都来自于boost的文档。

 

例一:

// BoostLearn.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

 

#include <fstream>

 

// 包含以简单文本格式实现存档的头文件

#include <boost/archive/text_oarchive.hpp>

#include <boost/archive/text_iarchive.hpp>

 

/////////////////////////////////////////////////////////////

// gps 座标

//

// 举例说明简单类型的序列化

//

class gps_position

{

private:

    friend class boost::serialization::access;

    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive

    // 是一个输入存档,则操作符& 被定义为>>.

    template<class Archive>

    void serialize(Archive & ar, const unsigned int version)

    {

       ar & degrees;

       ar & minutes;

       ar & seconds;

    }

    int degrees;

    int minutes;

    float seconds;

public:

    gps_position(){};

    gps_position(int d, int m, float s) :

    degrees(d), minutes(m), seconds(s)

    {}

};

 

int main() {

    // 创建并打开一个输出用的字符存档

    std::ofstream ofs("filename");

 

    // 创建类实例

    const gps_position g(35, 59, 24.567f);

 

    // 保存数据到存档

    {

       boost::archive::text_oarchive oa(ofs);

       // 将类实例写出到存档

       oa << g;

       // 在调用析构函数时将关闭存档和流

    }

 

    // ... 晚些时候,将类实例恢复到原来的状态

    gps_position newg;

    {

       // 创建并打开一个输入用的存档

       std::ifstream ifs("filename", std::ios::binary);

       boost::archive::text_iarchive ia(ifs);

       // 从存档中读取类的状态

       ia >> newg;

       // 在调用析构函数时将关闭存档和流

    }

    return 0;

}

 

首先,对于两个archive类我并不是很熟悉,这里摘其代码:

 

template<class Archive>

class interface_oarchive

{

protected:

    interface_oarchive(){};

public:

    /////////////////////////////////////////////////////////

    // archive public interface

    typedef mpl::bool_<false> is_loading;

    typedef mpl::bool_<true> is_saving;

 

    // return a pointer to the most derived class

    Archive * This(){

        return static_cast<Archive *>(this);

    }

 

    template<class T>

    const basic_pointer_oserializer *

    register_type(const T * = NULL){

        const basic_pointer_oserializer & bpos =

            boost::serialization::singleton<

                pointer_oserializer<Archive, T>

            >::get_const_instance();

        this->This()->register_basic_serializer(bpos.get_basic_serializer());

        return & bpos;

    }

 

    template<class T>

    Archive & operator<<(T & t){

        this->This()->save_override(t, 0);

        return * this->This();

    }

   

    // the & operator

    template<class T>

    Archive & operator&(T & t){

        #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING

            return * this->This() << const_cast<const T &>(t);

        #else

            return * this->This() << t;

        #endif

    }

};

 

这个函数的实现很有意思,利用了模板参数,然后强转,最后通过子类一层一层的传递,导致这里强转实际得到的是最深层次子类的指针。技巧性非常强:)虽然我实际中从来没有用到过-_-!这里相当于父类在实现某些函数的时候,直接使用子类的函数实现。

    Archive * This(){

        return static_cast<Archive *>(this);

 

就例子中

oa << g;

一句,其中函数来回调用,从子类到父类再到子类再到父类....#@$#@%#@相当的扭曲,利用的就是上面的This指针函数形式,为什么用这么多的语句来实现本来一句简单的memcpy就能完成的功能,值得思考,也许要进一步对其整体的框架构造有所了解才行。但是这一点在文档中肯定是没有论述的,我们寻找答案的唯一方法就是源代码了。

先将其类互相之间的关系锊一下。

text_oarchive继承自参数化类text_oarchive_impl,主要功能全由其提供。

text_oarchive_impl参数化类继承自basic_text_oprimitive<std::ostream>与参数化类basic_text_oarchivetext_oarchive_impl自身实现的代码主要是对各个字符串(包括char*,wchar*,string,wstring)的序列化。

text_oarchive_impl又是从参数化类basic_text_oprimitivebasic_text_oarchive继承过来。这里最好是画个UML图那就清晰了。但是由于我是如此的懒,所以我没有画-_-!

 

其中basic_text_oprimitive的实现,告诉了我们,为什么需要This函数来调用子类的函数。

通过跟踪源代码的boost::archive::text_oarchive oa(ofs);

此句,会发现,最终ofs这个ofstream最终是传入到了这个参数化basic_text_oprimitive,并作为其引用成员变量os保存的,然后basic_text_oprimitive C++basic类型的save函数重载,而save函数实际的实现又都是通过os <<操作符来实现的。

 

这里最引人注目的就是&操作符的重载,使得使用起来非常方便,兼有使用Serialize函数和<<,>>操作符的两种方案的好处。

 

我刚开始工作的时候以为序列化最大的好处就是对类的类成员变量与普通变量都使用了统一的处理接口,这自然也是序列化所需要的基本功能之一。