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

序列化支持(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函数和<<,>>操作符的两种方案的好处。

 

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

 

可序列化的成员

一下例子从文档衍生而来,经过添加必要代码使其可以执行

// 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(){ }

};

 

 

int main() {

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

    std::ofstream ofs("busfile");

 

    // 创建类实例

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

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

 

    bus_stop stop(latitude, longitude);

 

    // 保存数据到存档

    {

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

       // 将类实例写出到存档

       oa << stop;

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

    }

 

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

    bus_stop newstop;

    {

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

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

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

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

       ia >> newstop;

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

    }

    return 0;

}

 

 

这样,对于gps_positon这样的类成员变量,由于为其写过序列化函数了,就可以直接将其序列化

在这里是用:

       ar & latitude;

       ar & longitude;

的形式,这就是我刚开始唯一知道的序列化的好处。

 

 

当然,对于派生的类,应该也能调用基类的序列化函数,这在C++中也应该属于序列化的基本功能。

见下例:

// 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;

    }

};

 

 

 

int main() {

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

    std::ofstream ofs("bus_corner");

 

    // 创建类实例

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

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

 

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

 

    // 保存数据到存档

    {

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

       // 将类实例写出到存档

       oa << stop_corner;

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

    }

 

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

    bus_stop_corner new_stop_corner;

    {

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

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

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

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

       ia >> new_stop_corner;

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

    }

    return 0;

}

 

可以尝试调试程序,这里为了简化代码,我没有将信息输出了。

至此,我们已经有了序列化需要的基本功能了,至于其他更多复杂的结构不过就是此种方式的组合而已。另外,此种使用方式已经超过了我公司的序列化类-_-!那个类实现是非常简单的,但是功能似乎也是如此的弱,但是这里的超过不过是通过&符号的重载来简化Serialize函数的调用而已,还算不上什么质的飞跃。下面看质的飞跃所在。

  

 

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

 

 

 

 

阅读全文....

序列化支持(2)—Boost的序列化库


序列化支持(2)—Boost的序列化库

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

讨论新闻组及文件

这里找到了一些序列化的库,MFC的序列化库不能在Linux下用,剩下可以尝试的还有s11nCommonC++,和boost的序列化库了。。。特别要提到的就是boost的库。。。。一个序列化的库也写了230K,真是服了。。。。基本上我在公司写的序列化类也就一个文件一个类,也就1K,包括了所有的基本结构。。。很显然,boost的野心是很大的。。。出于对准标准库的尊敬。。。自然优先boost。特别要提到的是,Boost有所有我需要的东西-_-!并且我发现BoostASIO库就是我以前计划完成的目标。。。。。以后有特别有文章详细提到。。。

 

这里感谢为Boost库进行中文文档性工作的哥们,实在是感谢,虽然我常常以学英文为由去参看原文文档,甚至还阅读过基本原著,但是在文中引用的时候插入一篇的英文似乎是不太合适的,并且当我需要很快知道答案的时候,中文能够让我更快的处理,感谢你们。

以下引号中内容摘自Boost中文文档1.37.0版本:

这里有个对序列化比较正统的解释

“这里,我们用术语 "serialization序列化" 来表示将任意一组C++数据结构解构为一串字节的、可逆的过程。这样的系统可用于在另一个程序上下文中重新构建一个等价的结构。根据不同的上下文,它可以用来实现对象持久化、远程参数传递或其它功能。在本系统中,我们使用术语 "archive存档" 来指代这个字节流的特定表现。它可以是一个二进制数据文件、文本文件、XML或其它由本库的用户所创建的东西。”

注意,序列化的作用如我之前所述有:实现对象持久化、远程参数传递

 

我们对于这个系统的目标是:

  1. 代码的可移植性 - 只依赖于 ANSI C++ 所提供的功能。
  2. 代码的经济性 - 使用C++的一些特性,如 RTTI, 模板, 和多重继承等等,以使得代码更短也更易于使用。
  3. 各个类定义版本的无关性。即当一个类的定义更改时,旧文件仍可导入到新版本的类中。
  4. 深的指针保存和恢复。即指针的保存与恢复分别保存和恢复所指的数据。
  5. 对共享数据指针的正确恢复。
  6. STL容器及其它常用模板的序列化。
  7. 数据的可移植性 - 在一个平台上创建的字节流可以在另一平台上读出。
  8. 类的序列化与存档格式的正交性。即任何文件格式都可用于保存任意一组C++数据结构的序列化信息而无需调整。
  9. 非介入性。可以对不作更改的类进行序列化。即不要求进行序列化的类派生自某个特定基类或者实现特定的成员函数。这一点对于要将序列化应用于某些我们不能或不愿修改的类库中的类来说是十分必要的。
  10. archive 的接口必须足够简单,以易于创建一种新的存档类型。
  11. archive 的接口又必须足够丰富,才可以创建出象XML这样风格的存档。

野心够大,其描述的第17点很符合我的需要,第6点是属于额外的好处。

我们公司的序列化方式是,对于已实现序列化的结构使用其结构的Serialize方法,对于基本结构使用序列化类的Serialize,因为序列化类已经重载了基本结构的的Serialize实现。我曾经对比过这种方法和MFC的序列化方式,还有stream的使用方式,我感觉假如都使用stream那样重载<<,>>操作符的方式是最能节省击键数的,并且也足够的形象,并且个人认为,在某种程序上来说,stringstream就可以做一个简单的Archive实现来用。但是这里有一个比较不方便的就是,都需要先判断是存储还是读取,然后分别调用操作符,这里就相当于多了一倍的工作量,而公司的方式(统一到Serialize函数),虽然多了函数名的输入,但是对于输入还是输出可以不关心,这是其很大的优势。当时一下子还分不清孰优孰劣,但是出于亲近C++ 流实现的方式。碰到Boost的重载&操作符方式一下子傻了。。。。呵呵,听了太多的教条,太多的教导告诉我们不要重载操作符进行莫名奇妙的操作,除了真正用到其操作符原始涵义的时候,我还真从来没有去重载过他们,这里算是见识到了,虽然是重载了与操作符进行序列化运算,有点扭曲,但是,一切是简单就好,下面的示例你可以看到,这样兼有我上面所述的两种方案的优点。

       回想起工作当中用了多少CTRL-C,CTRL-Vyy,p)去实现该死的一行又一行的序列化啊,在我们公司吓死人的物品扩展属性中,有60个以上的字段。。。-_-!也记不清多少次添加成员变量却忘写序列化函数导致的bug,虽然这样的bug都都容易找,但是都出在与同事联合调试的时候,还是浪费了很多时间。Boost的这种方案仅仅也是简化了实现,并没有办法根除这一点,还是必须说明。虽然其提供了不介入的方式来完成序列化,但是本质上序列化函数还是需要自己来写,以后要是有种方式能够为用户新添的任何结构或类进行序列化,而不需要用户进行额外的工作时,那才是完美的序列化方案。。。。毕竟,越少的工作,出错的机会也就越少。

       现在想起来,重复工作最多的几个地方在哪?其一,结构的拷贝构造函数和=操作符的重载,为了安全,公司都是一个字段一个字段的写,累死人,也容易错。其二,就是一个字段一个字段的写序列化了。其三,目前还没有解决办法,数据库的操作,无论是MySQLC API还是ODBC,都是有多少个字段(呵呵,多少个“?”)就得bind多少个结构的数据。。。。也是累死人。。。想想我的两个记日志工作就心寒(其一就是日志服务器,另外还有监控中心对整个服务器运行情况的日志记录),那个重复工作的啊。。。。还好有vim:)以前还特意讲过怎么用vim来简化我的工作,没有vim我都吐血了。最好用的就是q的记录功能和ctrl+a的联合使用了(可惜Viemu不支持ctrl+a

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

 

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

 

阅读全文....

windows/linux服务器程序支持库的开发(2)--序列化支持(1)


windows/linux服务器程序支持库的开发2--序列化支持(1)

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

讨论新闻组及文件

没有工作前,不知道序列化的作用。。。。虽然那时候学习《Programming with MFC》的时候,也知道CArchiveCObject等东西,但是没有太意识到其作用。但是,如前所述,我工作的第一件事情,就是了解公司的序列化类。从当时的一无所知,到现在也慢慢理解了一些序列化的作用了。说起来,自从工作以来因为当时做文件系统后后来的程序补丁,我应经在公司的序列化类以外额外实现了两个独立的序列化类了,分别是文件序列化和内存序列化。

这里也顺便讲讲序列化吧,虽然不算太难,也不算太复杂,但是这样越是基础的东西越是支持着程序:)没有还真不行。

序列化的作用包括,实现操作系统,硬件平台无关的数据保存和传输。从网络传输角度来讲解决诸如大头,小头等问题。目前我自己的感受还有,对于保存数据和读取数据使用同一套接口,简化了数据结构的管理,方便使用。虽然,序列化的作用一般是用来做永久保存的,在《深入浅出MFC》中,侯捷就将其中的序列化技术讲解称为“VC++六大关键技术”,事实上,序列化的作用不仅仅如此,比如我以前不就写过一个内存序列化类吧:)公司还有一个网络序列化类,即便在MFC中,也有可以序列化的CMemFile,事实上,序列化还是一种二进制的交流方式。:)

最典型的网络程序,就更好理解了,网络上交流的其实都是二进制的数据,从一端到另外一端,大家要有统一的格式支持才能和谐-_-!不仅仅是字符串的编码需要一致,字节的顺序需要一致,每个位置是什么值自然也需要一一指定好,一般而言,对于网络程序,在我们公司,对于序列化成一个包,我们叫打包,从一个网络包反序列化(似乎也有人叫串行化),我们叫解包。

这种二进制交流的方式不仅限于网络程序,任何程序间的交流都有可能用到,我碰到另外一个典型情况是一个Python脚本希望将一些数据经过一段C++程序,然后再放到另外一段Python脚本中去,这时当然可以通过PythonC API将所有的数据都一个一个转成C变量保存下来然后传出去,由C++程序运行时保存,然后再通过PythonC API一一转成Python的变量,这样做有很多不好的地方,其一是Python脚本间传递的数据对于C++程序来说不透明,任何改变都需要C++程序进行相应的更改,另外是每个变量都通过PythonC API去转换效率不高。其实这里就可以利用序列化成二进制数据来传输,其上两个缺点都会消失。顺便提一下,在Python中可以通过struct库的pack,unpack函数进行简单数据的序列化,通过marshal库来进行复杂对象的序列化(可是这个库好像已经不被推荐,但是我当时在实际工作中使用的就是这个),有更先进的pickle(有更快的C语言版本cPickle)shelve可用。从这点来看,又会感叹Python的库实在是太多,往往不是没有你想要的库,而是由于库太多,你不知道哪个好用-_-!

 

 

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

 

阅读全文....

UCS-2与UTF8之间的选择(5)--断然决定UTF-8


UCS-2UTF8之间的选择(5--断然决定UTF-8

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

讨论新闻组及文件

       想来想去,在Unicode这种问题上纠缠过多似乎意义不大,就如我以前说的,我认为将来很自然的不会再需要考虑这个问题,因为,未来总会统一到UTF-32上去,那时候我现在考虑的这些东西都是完全没有意义-_-!在一个不久的将来就会完全没有意义的事情上考虑来考虑去似乎太没有抓住主要问题......

       最后我的决定是使用UTF-8,这是个背Windows的选择,但是是个亲开源的选择,亲近世界的选择......即便到了现在,世界上还是有很多程序员不使用Unicode,即便是在Windows下编程。

       举个让人信服的例子就是breakpadgoogle-breakpad - Google Code),这个程序对错误的处理技术可谓手段用尽,为了在程序出错下不破坏现场的手段现在还感叹精巧,不能说开发人员没有水平,但是,在Unicode模式下编译就会有严重的问题(我去年刚开始的第一个工作就是这个,现在不知道修复了没有)。只能感叹英语的强势,无论是在文化领域还是在编程领域都是一样。。。。。对于更多的开源项目,可以想象情况怎么样。。。。。

       另外,即便是一般的开源项目考虑了国际化,选择了Unicode来进行开发,一般而言,就目前的情况来看,似乎也是UTF-8居多,因为以前谈到的那么多的UTF-8的优点,其中最最符合欧美人习惯的一点就是完全兼容ASCII。。。。作为以自己语言开发的项目,移植到UTF-8一般而言比移植到UCS-2要更加方便,再加上开源世界的主导GNU/Linux的核心Linux的核心(好像有点绕)就是UTF-8的,所以,开源软件一般使用UTF-8也就很好理解了。就个人的开发学习而言,肯定会用到很多开源软件,或者参考,或者直接复用,当我的开发也是以UTF-8为基础的话,可以省事很多。举个简单的例子就是,CEGUI就是UTF-8的,这点我甚至看到Windows下的开发人员骂过,但是,这就是世界的现实。另外,就我使用的经验而言,MySQL也是对UTF-8的支持更加好,虽然其支持UCS-2编码的字符串,但是在命令行控制时无法正确显示,MySQL的著名前端工具,PhpAdmin也是对UTF-8支持良好,一样也无法显示UCS-2编码的字符串。(这些都是基于以前工作中的经验,现在不一定准确)作为个人开发。。。。我好像最可能使用的数据库也就是MySQL了。。。SQL Server我用过,的确是对UCS-2支持的更好,但是即便实在要用,还是可以现转成UCS-2的。Oracle没有用过,我不做评论。

       另外,本人在公司常常是在VS2005下做Windows的开发,虽然也有Linux的工作,但是一般都仅仅是将Windows下开发好的服务器移植到Linux下的工作,其实对Linux下的学习不够深入,对Linux下的服务器开发学习也不够深入,而且以前说过,公司为了更加满足Windows下的客户端的要求,所以公司都是使用UCS-2作为编码,即便是Linux下的服务器都是强制使用了2字节的wchar_t来适应这种选择,也逼迫公司需要重编译所有其想要在Linux下使用的库。。。因为也需要这些库使用2字节的wchar_t,其实代价也挺大的。。。。而且作为学习,要是老是和工作中做一样的事情又怎么能更多的学到新东西呢,又何谓工作外的学习。。。。。再加上目前个人对Linux有很大的兴趣,希望多学点东西,这也是原因之一,虽然我的目标是可移植,但是感觉以后可能还是以在Linux下跑服务器为主,那么简化Linux下的开发,并优化速度,也算是理由之一吧。

       再说,习惯了VS的我一直希望能将自己搬迁到Eclipse这样的多功能开放平台上,(虽然VS好像也可以作为开放式平台,但是相对来说插件的选择和质量实在和Eclipse不是一个量级的),虽然我目前没有学习JAVA,但是Eclipse也是我感觉非常合适的开发平台,不仅仅当我想使用C时,使用C++时,当我想使用Python,Lua时,这一样也是个很好的平台,而且,最重要的是,Linux下也能使用,在Linux下还能用其来开发bash脚本:)这一点是VS怎么也不可能达到的。。。。(我不期望MS将来开发Linux下的VS),另外,EclipseUTF-8的支持很好,而且也有vi的插件可用(Eclipse下的vi插件没有VS下的这么强大,支持的功能比较弱。。。这点比较遗憾。。。。),何况,假如哪天我希望学习JAVA。。。那么连IDE学习的时间都省了。。。。对于开发工具。。。实在可以有很多话说,越是学的语言更多,越是觉得为每一种语言去学习一个开发平台是不值得的,假如有一个比较统一的平台,那样可以节省很多时间,目前来说,最最能够胜任这个目标的就是Eclipse了,感谢IBM。。。。。目前我的暂时替代品就是VIM...文本编辑之王,写任何程序都感觉非常爽,就是配置起来有点麻烦,除了编辑以外的事情处理起来效率稍微低了点,调试更是弱项。。。。虽然其一向贯彻着一个程序只做好一件事情的原则,让其编辑能力无可匹敌(真的好用),但是当我需要更多丰富功能的时候。。。。实在有点麻烦。。。呵呵,我前段时间学习的时候,基本都是用vim编辑,用gdb/pydb/bashdb调试(没有找到好用的LUA调试工具,这也是我最近很少用LUA的原因,甚至EclipseLUA开发插件都不支持调试-_-!)对了,还有汇编,VS2005即便加上VA,汇编程序的开发也没有任何帮助。。。。这在我前段时间学习反汇编和内嵌汇编的时候感受很深,希望Eclipse不也要这样。。。。呵呵,也许额外还要多说一下Eclipse的是,多多使用GCC对于简化移植会有更多帮助,唯一的麻烦可能就是很多人可能会排斥不能用VS编译的程序-_-!这就是MS垄断的影响力所在。

       似乎好像谈多了Linux就自然会讲到很多MS的坏话-_-!这几乎成了Linux社团的特点。。。。其实我并不讨厌Windows,用某人的话来说,即便某天Windows不再垄断,仅仅占有30%的市场份额,为Windows做开发的程序员仍然有广阔的空间。。。。何况现在呢-_-!我仅仅是刚开始学编程,希望多接触到一些思想,另外为了学习编程,需要看更多好的优秀的程序,而那些开源的优秀的程序往往是运行在Linux下的,Windows下的优秀程序虽然很多,但是苦于人家不让我看源码啊。。。。-_-!

唯一还需要看的问题是Python的支持,要是Python仅仅支持UCS-2,那么我还是会改变我的决定。可惜:)Python的默认Unicode实现就是UTF-8编码的,更为强大的是,Python的正则表达式库还对其UTF-8有直接支持,太强大了,这一直被视为UTF-8编码的弱项,那么,还缺少什么?万事俱备,连东风都不欠了。

对了,作为我关注的语言之一,LUA直到5.1版本都没有对Unicode有直接的支持。。。这点比较遗憾。至于Bash...我要他支持Unicode干什么-_-!

 

       当仅在windows来进行编码转换时,就可以利用Windows的编码转换APIWideCharToMultiByteMultiByteToWideChar函数对,还算是比较好用,起码比Unicode组织提供的好用的多。其实还有C 运行库mbtowc, wctomb可以用,这两个函数在Linux下也有实现。那么,以后就不在这个问题上再进行更多的纠缠了,UTF-8吧:)

 

 

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

 

阅读全文....

UCS-2与UTF8之间的选择(4)--linux中各编码字符串的C/C++输出支持及方式比较


UCS-2UTF8之间的选择(4--linux中各编码字符串的C/C++输出支持及方式比较

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

讨论新闻组及文件

继续研究UTF8UCS-2的选择,这里继续使用上一次提到的函数。

鉴于大家不一定能找到下载的地址,而源文件是允许自由散发的,我将代码打包,提供给大家下载,下载地址还是在讨论新闻组及文件中,名字为unicodeorg.rar

 

昨天看了下Windows下的方法,这次研究Linux下的:

 1 #include <stdio.h>
 2 #include <locale.h>
 3 #include <stdlib.h>
 4 #include "ConvertUTF.h"
 5
 6 int main(int argc, char* argv[])
 7 {
 8
 9     ConversionResult result = sourceIllegal;
10     UTF16 utf16_buf[3] = {0};
11     utf16_buf[0] = 0x4e2d;
12     utf16_buf[1] = 0x6587;
13     utf16_buf[2] = 0;
14     UTF16 *utf16Start = utf16_buf;
15     UTF8 utf8_buf[12] = {0};
16     UTF8* utf8Start = utf8_buf;
17
18     // If you want to test next line, you can't get anything but ???
19     // wprintf("%s/n", utf16_buf);
20
21     result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
22     switch (result) {
23         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
24         case conversionOK: break;
25         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
26         case targetExhausted: printf("targetExhausted/t"); exit(0);
27         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
28     }
29
30     int i = 0;
31     for(; i < 12; ++i)
32     {
33         printf("%x ", utf8_buf[i]);
34     }
35     
36     printf("/n");
37
38     printf("%s/n", (char*)utf8_buf);
39     bzero(utf16_buf, sizeof(utf16_buf));
40
41     UTF8* utf8End = utf8Start;
42     utf8Start = utf8_buf;
43     utf16Start = utf16_buf;
44
45     result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
46     switch (result) {
47         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
48         case conversionOK: break;
49         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
50         case targetExhausted: printf("targetExhausted/t"); exit(0);
51         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
52     }
53
54     // If you want to test next line, you can't get anything
55     wprintf("%s/n", utf16_buf);
56
57     return 0;
58 }
59

 

运行结果:

e4 b8 ad e6 96 87 0 0 0 0 0 0

中文

 

这里和Windows中不同的就是输出UTF-8的字符串在Linux下甚至不需要通过setlocale设置环境变量,这样对于C++的输出估计还是有好处的:),起码不会去影响到C++的正常输出。但是,对于宽字节的输出没有办法成功,就算你像在Windows中设置locale也没有用,道理也很简单,因为Linux下的locale我就是设置成UTF-8-_-!

 

 1 #include <stdio.h>
 2 #include <locale.h>
 3 #include <stdlib.h>
 4 #include <iostream>
 5 #include "ConvertUTF.h"
 6 using namespace std;
 7
 8 int main(int argc, char* argv[])
 9 {
10     ConversionResult result = sourceIllegal;
11     UTF16 utf16_buf[3] = {0};
12     utf16_buf[0] = 0x4e2d;
13     utf16_buf[1] = 0x6587;
14     utf16_buf[2] = 0;
15     UTF16 *utf16Start = utf16_buf;
16     UTF8 utf8_buf[12] = {0};
17     UTF8* utf8Start = utf8_buf;
18
19     result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
20     switch (result) {
21         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
22         case conversionOK: break;
23         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
24         case targetExhausted: printf("targetExhausted/t"); exit(0);
25         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
26     }
27
28     int i = 0;
29     for(; i < 12; ++i)
30     {
31         printf("%x ", utf8_buf[i]);
32     }
33     
34     printf("/n");
35
36     cout << (char*)utf8_buf <<endl;
37     bzero(utf16_buf, sizeof(utf16_buf));
38
39     UTF8* utf8End = utf8Start;
40     utf8Start = utf8_buf;
41     utf16Start = utf16_buf;
42
43     result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
44     switch (result) {
45         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
46         case conversionOK: break;
47         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
48         case targetExhausted: printf("targetExhausted/t"); exit(0);
49         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
50     }
51
52     return 0;
53 }
54

 

直接可以获得

e4 b8 ad e6 96 87 0 0 0 0 0 0

中文

的输出,也不需要调用C++locale(imbue)函数去改变流的状态,甚至可以说,使用了UTF-8不仅仅是对ASCII完全的兼容了,对于新添加的字符也是可以不改变任何代码就做到兼容的,这点给我印象深刻。难怪UTF-8虽然是变长的编码方式,但是还是获得了这么广范围的应用。

这样的情况下,在Linux下同时在C C++中使用UTF-8输出中文也不会有任何冲突,这个优势比在Windows下大多了。

另外,有个很重要的问题需要提及的就是,目前我使用的Ubuntu8.04桌面版,不支持全Unicode的编码,似乎中文是没有什么问题,但是以前那几个太玄经的字符是输出不了的,可能其Unicode的支持只到到BMP这一层,这点比Windows稍逊色。以前测试过,的确是输出不了,但是源代码已经被覆盖了,大家可以自己去确定一下:)

 

 

 

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

 

阅读全文....