Creative Commons License
本Blog采用 知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议 进行许可。 —— Fox <游戏人生>

游戏人生

游戏人生 != ( 人生 == 游戏 )
站点迁移至:http://www.yulefox.com。请订阅本博的朋友将RSS修改为http://feeds.feedburner.com/yulefox
posts - 62, comments - 508, trackbacks - 0, articles - 7

本文同时发布在

近来在Windows下用WSAEventSelect时,碰到一个棘手的问题,当然现在已经解决了。

问题描述:

一个Server,一个ClientA,一个ClientB,Server用WSAEventSelect模型监听(只有监听,没有读写),ClientA在连接Server后,ClientA对应的EventA被触发,Server的WSAWaitForMultipleEvents等待到EventA,ClientB连接Server时,TCP三次握手成功,ClientB与Server的TCP状态被置为ESTABLISHED,然而Server的WSAWaitForMultipleEvents没有等待到EventB被触发。

用netstat看了一下,ClientB与Server的状态是ESTABLISHED,此时如果ClientB退出,由于Server无法正常Close该连接,因此Server的状态不是TIME_WAIT而是CLOSE_WAIT(持续2小时),Client的状态是FIN_WAIT_2(持续10分钟)。

我尝试将ClientA主动关闭后再次连接Server,Server的WSAWaitForMultipleEvents在wait到EventA之后,EventB此时也被触发。

开始一直以为问题的根源在于WSAEventSelect的使用上,毕竟,之前没有系统写过类似的代码,难免怀疑到事件模型的使用上。多方查阅资料,最后还是没有发现类似问题的解决方案。

又跟了一上午之后,Kevin开始怀疑是多线程使用的问题,我看了一下,的确没有对event的多线程操作进行处理,但因为在另一个应用中,使用了同样的模块,却没有该问题。最后考虑必要性时还是放弃了加临界资源,无视多线程同步问题。Kevin本来劝我换个模型,但我固执的认为要做就把这事儿做好。因为下午还要回学校一趟,就想尽快搞定,毕竟因为这一块已经把Kevin的进度拖了一周了,心下还是过意不去,而且隐约感觉到离问题的解决越来越近了。

问题分析:

在对着WSAWaitForMultipleEvents思考了半天之后,忽然开窍了,如果ThreadA在WSAWaitForMultipleEvents时,只有一个EventA被WSAEventSelect并set到signaled状态,则该EventA会被wait成功,ThreadA处理EventA之后继续阻塞在WSAWaitForMultipleEvents。此时,ThreadB通过WSAEventSelect将EventB初始化为nonsignaled状态,之后即使EventB被set为signaled状态,但ThreadA的WSAWaitForMultipleEvents因为处于阻塞状态,不可能刷新事件集,也就不可能wait到EventB,最终导致了ClientB的请求无法被响应。如果EventA被触发则会被ThreadA等待到,WSAWaitForMultipleEvents返回后再次进入时事件集已经被刷新,EventB被wait到也就不难理解了。

问题解决:

说到底是因为当ThreadA阻塞在WSAWaitForMultipleEvents处之时,事件集的变更无法立即得到体现。如果允许上层应用随时create或close一些event,则WSAWaitForMultipleEvents就不应该无限阻塞下去。

因此最后的一个解决方法就是让WSAWaitForMultipleEvents超时返回并Sleep一段时间,当WSAWaitForMultipleEvents再次进入时事件集得以更新。

想了一下,另一个应用中之所以没出现该问题也只是个巧合,因为该应用中ThreadB的两次WSAEventSelect间隔很短,在ThreadA获得时间片之前已经确定了事件集。

说白了这也不是一个什么大问题,甚至谈不上任何难度,但是因为之前对WSAEventSelect没有一个清晰的概念,因此在发现和分析问题上花费了大量时间,加上在VS2005调试过程中,有个别文件更新时没有被重新编译,也耗费了很多无谓的时间,以至于我们都在考虑是不是要放弃IDE,因为我们确实太依赖IDE了,有些TX为了稳妥,每次都是“重新生成整个解决方案”,如果一个解决方案有几千个文件、几十万行的代码,估计重编一次也要花个几分钟吧。

总结:

  1. netstat观察的网络连接处于ESTABLISHED状态并不意味着逻辑连接被accept,只是表明客户端connect的TCP物理连接(三次握手)被服务器端ack,如果服务器没有accept到该连接,证明网络模块代码有问题;
  2. 多线程怎么都是个问题,线程同步尽量避免,毕竟,用Kevin的话来说,加锁是丑陋的。但在涉及到同步问题时,还是权衡一下,我这儿之所以最后没有加临界区,是因为事件主要是在ThreadA中处理,ThreadB中只有create操作,而且ThreadA对事件集的刷新要求不是那么严格,也就不考虑加临界区了;
  3. 如果能力和条件允许的话,放弃IDE吧,IDE的确不是个好东西,我主要是指在编译链接的时候,如果作为编辑器说不定还会好用:)。

个人网站用的主机最近从据说要黑屏的Windows换成了Debian,还在调整,估计明天能弄好,内容肯定比Cppblog杂的多,谈点技术的还是会同步更新到

posted @ 2008-10-27 23:25 Fox 阅读(5552) | 评论 (3)编辑 收藏

作者:Fox

本文同时发布在http://www.yulefox.comhttp://www.cppblog.com/fox

十天之前,在CPPBLOG上写了一篇,有同学提到该实现不支持成员函数。这个问题我也考虑到了,既然被提出来,不妨把实现提供出来。

需要说明的是,我本身对template比较不感冒,不过对template感冒,而且写过关于成员函数指针的问题,想了很久,如果支持成员函数指针,不用模板是不行了。

此处对成员函数的支持还不涉及对函数参数的泛化,因为我这个消息映射暂时不需要参数泛化,下面的代码应该不需要过多的解释了。

#define REG_MSG_FUNC(nMsgType, MsgFunc) \
    CMsgRegister::RegisterCallFunc(nMsgType, MsgFunc);

#define REG_MSG_MEM_FUNC(nMsgType, Obj, MsgFunc) \
    CMsgRegister::RegisterCallFunc(nMsgType, Obj, MsgFunc);

class CBaseMessage;

class CHandler
{
public:
    virtual int operator()(CBaseMessage* pMsg) = 0;
};

template<typename FuncType>
class CDefHandler : public CHandler
{
public:
    CDefHandler(){}
    CDefHandler(FuncType &Func)
        : m_Func(Func)
    {
    }

    virtual int operator()(CBaseMessage* pMsg)
    {
        return m_Func(pMsg);
    }

protected:
    FuncType    m_Func;
};

template<typename ObjType, typename FuncType>
class CMemHandler : public CHandler
{
public:
    CMemHandler(){}
    CMemHandler(ObjType* pObj, FuncType Func)
        : m_pObj(pObj)
        , m_Func(Func)
    {
    }

    virtual int operator()(CBaseMessage* pMsg)
    {
        return (m_pObj->*m_Func)(pMsg);
    }

protected:
    FuncType    m_Func;
    ObjType*    m_pObj;
};

class CFunction
{
public:
    CFunction()
        : m_pHandler(NULL)
    {
    }

    // 封装(C函数或静态成员函数)
    template<typename FuncType>
    CFunction( FuncType &Func )
        : m_pHandler(new CDefHandler<FuncType>(Func))
    {
    }

    // 封装(非静态成员函数)
    template<typename ObjType, typename FuncType>
    CFunction( ObjType* pObj, FuncType Func )
        : m_pHandler(new CMemHandler<ObjType, FuncType>(pObj, Func))
    {
    }

    virtual ~CFunction()
    {
        DELETE_SAFE(m_pHandler);
    }

        // 函数调用
    int operator()(CBaseMessage* pMsg)
    {
        return (*m_pHandler)(pMsg);
    }

private:
    CHandler    *m_pHandler;
};

typedef std::map<int, CFunction*> MSG_MAP;
typedef MSG_MAP::iterator MSG_ITR;

class CMsgRegister
{
public:
    // 注册消息函数(C函数或静态成员函数)
    template <typename FuncType>
    inline static void RegisterCallFunc(int nMsgType, FuncType &Func)
    {
        CFunction *func = new CFunction(Func);
        s_MsgMap[nMsgType] = func;
    }

    // 注册消息函数(非静态成员函数)
    template <typename ObjType, typename FuncType>
    inline static void RegisterCallFunc(int nMsgType, ObjType* pObj, FuncType Func)
    {
        CFunction *func = new CFunction(pObj, Func);
        s_MsgMap[nMsgType] = func;
    }

    // 执行消息
    inline static void RunCallFunc(int nMsgType, CBaseMessage* pMsg)
    {
        MSG_ITR itr = s_MsgMap.find(nMsgType);
        if( s_MsgMap.end() != itr )
        {
            (*itr->second)(pMsg);
        }
    }

    static void ReleaseMsgMap()                // 释放消息映射表
    {
        MSG_ITR itr = s_MsgMap.begin();
        while( itr != s_MsgMap.end() )
        {
            DELETE_SAFE(itr->second);
            itr = s_MsgMap.erase(itr);
        }
    }

protected:
    static MSG_MAP            s_MsgMap;        // 消息映射表
};

不可否认,模板给了你更大的想象空间,很多东西,还是不要一味排斥的好:)。

posted @ 2008-10-10 10:20 Fox 阅读(2659) | 评论 (4)编辑 收藏

作者:Fox

本文同时发布在http://www.yulefox.comhttp://www.cppblog.com/fox

两个多月之前,在CPPBLOG上写过一篇关于游戏开发中的问题,主要该考虑的问题都已经说明,当时没有实现这一块。在最近一个模块中,写了一个非常简单的写日志的接口,接口的声明大概是:

void PutoutLog(const char *szFile, const char *szLog, ...);

记录的日志格式如下:

1  2008-10-10-03:30:10.618 | projectpath/srcfile.cpp/function(30) : 哦嚯, 这儿出错了(eno : 0x00100000).

用到了__FILE__、__LINE__、__FUNCTION__几个宏。

基本满足需要了,需要改进的地方我现在能想到的主要是:

  • 文件名是全路径,没有必要,只记录文件名称其实就够了;
  • 没有考虑写日志时的线程同步问题;
  • 系统dump时的日志还是没有办法记录;
  • 缺少足够的、动态的上下文信息:调用堆栈、函数参数、系统运行参数;
  • 日志记录到普通文本中,虽然记录了时间、位置,还是不便于系统查看、查找、分析、挖掘。

说白了,这所谓的基本满足需要只是皮毛,因为最近在打理,有感于网页数据库技术的博大精深、美妙直观,如果可以把日志用网页数据库作为读写的载体,岂不甚妙?

隐约中感觉这种想法似曾相识,不识字只好乱翻书,果然在中发现有这样一篇文章:一个基于HTML的日志和调试系统。有兴趣的同学自己翻书吧:)。

如果将更加丰富的信息写入xml或php文件中,加入到数据库,可以对数据进行分析、挖掘,并友好的显示在浏览器中,这对于枯燥的debug过程,起码增添了些许益处。

然而,这又只是一个想法,或许在我手头上的工作稍后告一段落的时候,我可以花精力研究一下这方面的东西。

posted @ 2008-10-10 04:18 Fox 阅读(1813) | 评论 (8)编辑 收藏

项目中使用了消息通信机制,因为消息类型非常多,相应的,处理消息的地方代码也非常多。

自然而然想到MFC中的消息映射:

创建一个缺省MFC框架程序的解决方案Test,在Test.h中看到以下内容:

class Ctest_mfcApp : public CWinApp
{
public:
    Ctest_mfcApp();

// 重写
public:
    virtual BOOL InitInstance();

// 实现
    afx_msg void OnAppAbout();
    DECLARE_MESSAGE_MAP()
};

 

其中,最紧要的就是DECLARE_MESSAGE_MAP()这个宏,相关内容展开如下:

struct AFX_MSGMAP_ENTRY
{
    UINT nMessage;   // windows message
    UINT nCode;      // control code or WM_NOTIFY code
    UINT nID;        // control ID (or 0 for windows messages)
    UINT nLastID;    // used for entries specifying a range of control id's
    UINT_PTR nSig;   // signature type (action) or pointer to message #
    AFX_PMSG pfn;    // routine to call (or special value)
};

struct AFX_MSGMAP
{
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
    const AFX_MSGMAP_ENTRY* lpEntries;
};

#define DECLARE_MESSAGE_MAP() \
protected: \
    static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
    virtual const AFX_MSGMAP* GetMessageMap() const; \

其中AFX_PMSG不再解析下去,我们认为这是一个指向特定消息对应的实现函数的函数指针,这几个宏的作用可简单理解成为Test这个项目定义了一个静态的消息映射表。当消息到来时,从消息队列中弹出消息并分发到具有入口实现的上层CWnd派生窗口。用户只需要注册消息,实现消息入口函数就够了,这在MFC中一般放在.cpp文件头部。Test.cpp中头部有以下内容:

BEGIN_MESSAGE_MAP(CTest, CWinApp)
    ON_COMMAND(ID_APP_ABOUT, &CTest::OnAppAbout)
    // 基于文件的标准文档命令
    ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
    // 标准打印设置命令
    ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()

这里是为消息枚举值与消息实现函数建立映射,其中涉及到的宏的展开如下:

#define ON_COMMAND(id, memberFxn) \
    { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \
        static_cast<AFX_PMSG> (memberFxn) },

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
    PTM_WARNING_DISABLE \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
        { return GetThisMessageMap(); } \
    const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
    { \
        typedef theClass ThisClass;                           \
        typedef baseClass TheBaseClass;                       \
        static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
        {

#define END_MESSAGE_MAP() \
        {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
    }; \
        static const AFX_MSGMAP messageMap = \
        { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
        return &messageMap; \
    }                                  \
    PTM_WARNING_RESTORE

按照上述思路得到的相似代码如下:

// Test.h
typedef void (* funCall)(void*);        // 消息执行函数类型

struct tagMsgEntry                      // 消息入口结构
{
    int            nMsgType;            // 消息类型
    funCall        MsgRun;              // 消息执行函数
};

struct tagMsgMap                        // 消息映射表结构
{
    const tagMsgMap* (__stdcall* funGetBaseMsgMap)();
    const tagMsgEntry* pMsgEntries;     // 消息入口集
};

class CMessage
{
    // ...
protected:
    static const tagMsgMap* __stdcall GetThisMsgMap();
    virtual const tagMsgMap* GetMsgMap() const;
};

// Test.cpp
const tagMsgMap* CMessage::GetMsgMap() const
{
    return GetThisMsgMap();
}

const tagMsgMap* __stdcall CMessage::GetThisMsgMap()
{
    static const tagMsgEntry MsgEntries[] =
    {
        { MSG_SOME_ONE, SomeOneFunc },
        { MSG_SOME_TWO, SomeTwoFunc },
        { MSG_NULL, NULL }
    };
    static const tagMsgMap msgMap =
    {
        &CBaseMessage::GetThisMsgMap,    // 父类消息映射表
        &MsgEntries[0]
    };
    return &msgMap;
}

int CMessage::MsgProc(int nMsgType)
{
    switch( nMsgType )
    {
    case MSG_SOME_ONE:
        {

        }
        break;
    }
    return CBaseMessage::MsgProc(nMsgType);
}

这种处理的优点在于,子类没有定义的消息实现接口,可以使用父类接口实现。不过在现在的消息处理中,我们一般不需要由基类来完成,因此可以不依赖基类接口,使用宏可以使代码看上去更加简洁。

___________________________________________________________

简化版本的消息映射采用以下方式,简单清晰:

// Test.h
#define REG_MSG_FUNC(nMsgType, MsgFunc) \
    CMessge::RegisterCallFunc(nMsgType, MsgFunc); \

typedef void (* function)(void*);

typedef std::map<int, function> MSG_MAP;
typedef MSG_MAP::const_iterator MSG_CITR;

class CMessage
{
    // ...
public:
    static const MSG_MAP& GetMsgMap();
    static void RegisterCallFunc(int nMsgType, void(* Func)(void *))
    {
        s_MsgMap[nMsgType] = Func;
    }

    int CMessage::Run(int nMsgType)                // 消息公用执行函数
    {
        MSG_ITR itr = s_MsgMap.find(nMsgType);
        if( s_MsgMap.end() != itr )
        {
            itr->second(this);
        }
    }

protected:
    static MSG_MAP            s_MsgMap;            // 消息映射表
};

// UserTest.cpp -- 用户在使用时对自己关心的消息予以注册, 入口函数予以实现即可
REG_MSG_FUNC(MSG_SOME_ONE, SomeOneFunc)

void SomeOneFunc(CBaseMessage *pMsg)
{
    return;
}

___________________________________________________________

最近忙的焦头烂额,正好写到消息,稍微整理一下,提供些许借鉴。

posted @ 2008-09-29 18:34 Fox 阅读(4431) | 评论 (31)编辑 收藏

网络编程学习和实践的过程中,同步(synchronous)/异步(asynchronous)阻塞(blocking)/非阻塞(non-blocking)总是会迷惑很多人。依然记得我半年之前在记述IOCP时,一句不经意的“非阻塞I/O则是致力于提供高效的异步I/O”便引来一番口水论争。

今天在查一些资料的时候,看到关于这几个词的论辩竟不是一般的多,细细想来,这个问题似乎也确实有解释的必要,不在于争论对错,而在于辨明是非。

讨论之前,先限定讨论的范围:此处之同步/异步仅限于I/O操作,与OS所讨论的进程/线程中的其他同步/异步没有直接关系;讨论的内容是:两对相似的术语之间的区别到底有多大

  • 非常大:

Douglas C. Schmidt在《C++网络编程》中这样说到:

They are very different, as follows:

AIO is "asynchronous I/O", i.e., the operation is invoked asynchronously and control returns to the client while the OS kernel processes the I/O request.  When the operation completes there is some mechanism for the client to retrieve the results.

Non-blocking I/O tries an operation (such as a read() or write()) and if it the operation would block (e.g., due to flow control on a TCP connection or due to lack of data in a socket), the call returns -1 and sets errno to EWOULDBLOCK.

翻译如下:

:例如,操作被异步调用时,控制权交给客户端,I/O操作请求则交由操作系统内核处理,当操作完成后,通过某种机制将结果通知客户端。

非阻塞I/O:尝试调用某操作,如果操作被阻塞,则调用返回-1并置错误值为EWOULDBLOCK。

从这两段“very different”的解释来看,我的感觉是并没有指出二者的区别,因为我们无法确定所谓AIO是如何处理的,如果AIO直接“调用返回-1并置错误值为EWOULDBLOCK”,实现“控制权交给客户端”,似乎并无任何不妥。况且,对于非阻塞I/O,我们也需要“当操作完成后,通过某种机制将结果通知客户端”这样的处理。

  • 无差别:

而在Wikipedia上则直接等同二者:Asynchronous I/O, or non-blocking I/O, is a form of input/output processing that permits other processing to continue before the transmission has finished.

当然,对于recv和send,我们一般会说他们是阻塞起的,而不会说他们是同步起的,但这显然不是二者的区别,因为我们都知道,阻塞的原因正是等待同步结果的返回

因此,二者的区别在于,阻塞/非阻塞是表现,同步/异步是原因,我们说某某操作是阻塞起的,或者某某线程是阻塞起的,是因为在等待操作结果的同步返回;我们说某某操作是非阻塞的,是因为操作结果会通过异步方式返回。

讨论到这儿,再咬文嚼字的争辩下去似乎已经没有任何实际意义。

------------------------------------------------------------

PS:纠结一些必要的概念是为了加深理解,太过纠结了反倒会滞塞理解。我之前对于其概念也并非特别清楚,所以才会再续一篇特意言明,也算弥补一下自己的过失。

posted @ 2008-09-11 01:11 Fox 阅读(4931) | 评论 (12)编辑 收藏

When :  2008.8.8.20:00:00

Where : National Stadium, Beijing, China.

Who :    One World.

What :   One Dream.

All right, it is just a D-R-E-A-M...

____________________________

但是,我一定会看,从电视上。

就像看火炬一定要从电视上才和谐好些,开幕式也是。

网上猜测李宁点火的人气很高,之前官方也有透露说会有5.12汶川大地震相关内容,我猜想是这样的:

李宁身着Li-Ning牌黑白熊猫运动服,像功夫熊猫那样,以体操功夫的糅合动作跳进8级地震中的深5.12m的名为汶川主火炬盆,以川剧中的吐火绝技点燃主火炬盆,高喊“我是李书记,快救我”,连做三个俯卧撑后,欲火浴火涅磐凤凰,手提一瓶和谐酱油飞向太空……

感觉很靠谱:D。

posted @ 2008-08-08 14:16 Fox 阅读(844) | 评论 (2)编辑 收藏

0. Introduction

接触设计模式有两年时间了,但一直没有系统整理过,为了不至于让自己的思维被繁琐的工作一点点禁锢,还是决定总结一下,为了能够真正做到有所收获,整个系列会按照GoF的Design Patterns: Elements of Reusable Object-Oriented Software的行文思路,但不会照本宣科就是了,Wikipedia上关于23种设计模式的介绍非常全面,CSDN上也可以下载中/英文电子档,因此很多套话、类图一概省去。

最早接触设计模式的时候,难免被各种模式的联系和区别所困扰,从教科书的分析可以得到模式之间形式上的不同。但这样对于领会设计模式意义不大,因为我们掌握模式的目的是为了融会贯通,灵活运用,以对开发有所帮助。

稍微成规模的OO程序,会有大量对象,其中很多实体对象之间存在着父子、兄弟关系,对象的创建提升为一种模式。其好处在于设计模式本身所宣称的reusable,这就像堆积木盖房子一样,堆的好的情况下,换一换门窗便是另一番风景。

关于实现,我不会为了厘清模式间的区别而刻意使用相似代码实现,相反,我会根据模式本身的适用情况举例,而且大量代码基于SourceMaking

_______________________________

1. Creational Design Patterns(DP)

创建型DP抽象了类和对象的创建过程,GoF给出了5种创建型DPAbstract FactoryBuilderFactory MethodBuilderPrototypeSingleton

2. Abstract Factory

意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

1) 只提供了一个创建接口,其返回值为具体产品:如AbstractProduct *Client::CreateProduct(AbstractFactory &factory);

2) 接口的参数是一个工厂对象AbstractFactory &factory)的引用,参数类型(AbstractFactory)为抽象基类,调用时根据需要传入具体工厂对象即可;

3) 接口内部实现了一系列相关或相互依赖对象(抽象产品)的创建:当传入具体工厂时,接口实现的就是一系列具体产品的创建;

4) 创建的产品立即返回CreateProduct)。

参与者:

• AbstractFactory
— 声明一个创建抽象产品对象的操作接口。

• ConcreteFactory
— 实现创建具体产品对象的操作。

• AbstractProduct
— 为一类产品对象声明一个接口。

• ConcreteProduct
— 定义一个将被相应的具体工厂创建的产品对象。
— 实现AbstractProduct接口。

• Client
— 仅使用由AbstractFactory和AbstractProduct类声明的接口。

代码:

class AbstractFactory
{
public:
    virtual AbstractProduct *MakePartA() = 0;
    virtual AbstractProduct *MakePartB() = 0;
    virtual AbstractProduct *MakePartC() = 0;
    virtual AbstractProduct *AddPart(const AbstractProduct *pPart) = 0;
};

AbstractProduct *Client::CreateProduct(AbstractFactory &factory)
{
    AbstractProduct *pProduct = factory.CreateProduct();
    AbstractProduct *pPartA = factory.MakePartA();
    AbstractProduct *pPartB = factory.MakePartB();
    AbstractProduct *pPartC = factory.MakePartC();
    factory.AddPart(pPartA);
    factory.AddPart(pPartB);
    factory.AddPart(pPartC);
    return pProduct;
}

int main(void)
{
    Client client;           
    ConcreteFactory factory;
    client.CreateProduct(factory);
    return 0;
}

3. Builder

意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

1) director提供抽象产品创建接口:如void Director::Construct();

2) 不同产品使用同一创建过程,由director指定特定builder以生产不同产品;

3) 接口内部实现了一个复杂对象(抽象产品)的创建:当传入具体工厂时,接口实现的是一个复杂的具体产品的创建;

4) 创建的产品并不立即返回创建完毕后返回,或使用接口GetProduct)提取结果。

参与者:

• Builder
— 为创建一个Product对象的各个部件指定抽象接口。

• ConcreteBuilder
— 实现Builder的接口以构造和装配该产品的各个部件。
— 定义并明确它所创建的表示。
— 提供一个检索产品的接口。

• Director
— 构造一个使用Builder接口的对象。

• Product
— 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。
— 包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

代码:

class Builder
{
public:
    virtual void MakePartA() = 0;
    virtual void MakePartB() = 0;
    virtual void MakePartC() = 0;

    Product *GetProduct()    { return _product; }

protected:
    Product *_product;
};

class Director
{
public:
    void setBuilder(Builder *b)    { _builder = b; }
    void Construct();

private:
    Builder *_builder;
};

void Director::Construct()
{
    _builder.MakePartA();
    _builder.MakePartB();
    _builder.MakePartC();
}

int main(void) {
    ConcreteBuilderA concreteBuilderA;
    ConcreteBuilderB concreteBuilderB;
    Director director;
    Product *pProduct;

    director.SetBuilder(&concreteBuilderA);
    director.Construct();
    pProduct = concreteBuilderA.GetProduct();
    pProduct->Show();

    director.SetBuilder(&concreteBuilderB);
    director.Construct();
    pProduct = concreteBuilderB.GetProduct();
    pProduct->Show();

    return 0;
}

4. Factory Method

意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。

1) 看得出该模式其实就是C++的多态特性,借继承实现。因此,其别名为虚构造器( Virtual Constructor)

2) 作为模式与C++多态特性不同的是,Creator可以定义工厂方法的缺省实现,完成缺省操作,MFC大量使用了这一思想。

参与者:

• Product
— 定义工厂方法所创建的对象的接口。

• ConcreteProduct
— 实现Product接口。

• Creator
— 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。
— 可以调用工厂方法以创建一个Product对象。

• ConcreteCreator
— 重定义工厂方法以返回一个ConcreteProduct实例。

代码:

ConcreteProduct *ConcreteCreator::FactoryMethod()
{
    ConcreteProduct *pProduct = new ConcreteProduct;
    return pProduct;
}

Product *Creator::FactoryMethod()
{
    Product *pProduct = new Product;
    return pProduct;
}

int main(void) {
    Creator creator;
    ConcreteProduct *pProduct;

    pProduct = creator.FactoryMethod();
    pProduct->Show();

    return 0;
}

5. Prototype

意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

1) 创建不再通过工厂新类继承(inheritance),而是通过委托(delegation)

2) 根通拷贝原型实例创建新对象。

参与者:

• ProtoType
— 声明一个克隆自身的接口。

• ConcreteProtoType
— 实现一个克隆自身的操作。

• Client
— 让一个原型克隆自身从而创建一个新的对象。

代码:

class ProtoType
{
public:
    virtual void Draw();
    virtual ProtoType *Clone() = 0;
    virtual void Initialize();
};

class ProtoTypeA: public ProtoType
{
public:
    virtual ProtoType *Clone()
    {
        return new ProtoTypeA;
    }
};

class ProtoTypeB: public ProtoType
{
public:
    virtual ProtoType *Clone()
    {
        return new ProtoTypeB;
    }
};

class Client
{
public:
    static ProtoType *Clone( int choice );

private:
    static ProtoType *s_prototypes[3];
};

ProtoType* Client::s_prototypes[] = { 0, new ProtoTypeA, new ProtoTypeB };

ProtoType *Client::Clone(int choice)
{
    return s_prototypes[choice]->Clone();
}

 

6. Singleton

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

1) 用静态成员函数保证上述意图。

参与者:

• Singleton
— 定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(即C++中的一个静态成员函数)。
— 可能负责创建它自己的唯一实例。

 

代码:

class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (!s_instance)
            s_instance = new Singleton;
        return s_instance;
    }

    void Run()    {}

private:
    static Singleton *s_instance;
    Singleton()    {}                // Singleton cannot be created outside.
};

Singleton *GetSingleton(void)
{
    return Singleton::GetInstance();
}

int main(void)
{
    GetSingleton()->Run();

    return 0;
}

______________________________________________

代码写的都比较简单,基本可以将各种模式之间的不同体现出来了。

posted @ 2008-08-06 15:43 Fox 阅读(2206) | 评论 (3)编辑 收藏

一、Big-endian & Little-endian

还是Wikipedia好啊!可惜中文的国内看不了,愚昧啊!实在觉得中文有点难懂,看看日本语版本吧:D!

关于端(endianness)的介绍,Wikipedia上比较全了:http://en.wikipedia.org/wiki/Endianness

关于网络字节序(network byte order)主机字节序(host byte order),说来挺无关紧要的一点东西,因为每次总是忘掉,所以每次都要好奇的看看大端(big-endian)小端(little-endian)

给定unsigned long型整数十六进制形式:0x0A0B0C0D,其big-endian和little-endian形式分别为:

1) Big-endian

Memory
|
...  |  8-bit atomic element size       | ...    |  16-bit atomic element size
| 0x0A |  a                               | 0x0A0B |  a
| 0x0B |  a+1                             | 0x0C0D |  a+1
| 0x0C |  a+2
| 0x0D |  a+3
| ...  |

2) Little-endian(X86)

Memory
|
...  |  8-bit atomic element size       | ...    |  16-bit atomic element size
| 0x0D |  a                               | 0x0C0D |  a
| 0x0C |  a+1                             | 0x0A0B |  a+1
| 0x0B |  a+2
| 0x0A |  a+3
| ...  |

Mapping registers to memory locations (from Wikipedia)

为什么X86存储会使用little-endian,起初我想对于位运算,尤其是位移运算,little-endian很方便,但转念一想,big-endian也方便啊,无非是左移和右移的区别而已,但little-endian的优势在于unsigned char/short/int/long类型转换时,存储位置无需改变。

在网络传输中,采用big-endian序,对于0x0A0B0C0D,传输顺序就是0A 0B 0C 0D,因此big-endian作为network byte order,little-endian作为host byte order。

________________________________________________

PS:做鸡有什么不好?

上午跟某同事(为尊重虑,下文以Y称之)躲在犄角旮旯抽烟。以下为场景再现:

(忽然整出来一句)Y:听过鹰的故事没有?

(满脸疑惑)Fox:没有。

Y:一只小鹰掉到鸡窝里,#$@%……

F:我不是鹰,我就是一只鸡,做技术鸡有什么不好?

Y:做技术没有不好啊……

F:我不是说做技术,我说做鸡,我就是在地上走的,我为什么总是要抬头看天?

Y:你要往上看,没有人注定不能飞,XX以前也没有想过有一天会飞起来。

F:我不是掉到鸡窝里,我本来就在鸡窝里,我也喜欢呆在鸡窝里,别人都在地上走,我为什么要飞起来?

Y:你总要飞起来。

F:我说了我喜欢呆在鸡窝里,你见过有那只鸡飞起来了?

Y:……

F:我就是一只鸡,插了鸡翅还是飞不起来,况且,我对飞起来也没有任何兴趣。

Y:……

F:做鸡有什么不好?

Y:你看老毛,与人斗其乐无穷,他境界多高,与天斗其乐无穷,知道吧,他已经不屑与人斗了。

F:我不喜欢与人斗,我也斗不过,做鸡有什么不好?

Y:……

posted @ 2008-07-30 14:48 Fox 阅读(2001) | 评论 (4)编辑 收藏

原文地址:

  • 规则之例外

前面说明的编码习惯基本是强制性的,但所有优秀的规则都允许例外。

1. 现有不统一代码(Existing Non-conformant Code)

对于现有不符合既定编程风格的代码可以网开一面。

当你修改使用其他风格的代码时,为了与代码原有风格保持一致可以不使用本指南约定。如果不放心可以与代码原作者或现在的负责人员商讨,记住,一致性包括原有的一致性。

1. Windows代码(Windows Code)

Windows程序员有自己的编码习惯,主要源于Windows的一些头文件和其他Microsoft代码。我们希望任何人都可以顺利读懂你的代码,所以针对所有平台的C++编码给出一个单独的指导方案。

如果你一直使用Windows编码风格的,这儿有必要重申一下某些你可能会忘记的指南(译者注,我怎么感觉像在被洗脑:D)

1) 不要使用匈牙利命名法(Hungarian notation,如定义整型变量为iNum,使用Google命名约定,包括对源文件使用.cc扩展名;

2) Windows定义了很多原有内建类型的同义词(译者注,这一点,我也很反感),如DWORDHANDLE等等,在调用Windows API时这是完全可以接受甚至鼓励的,但还是尽量使用原来的C++类型,例如,使用const TCHAR *而不是LPCTSTR

3) 使用Microsoft Visual C++进行编译时,将警告级别设置为3或更高,并将所有warnings当作errors处理

4) 不要使用#pragma once;作为包含保护,使用C++标准包含保护包含保护的文件路径包含到项目树顶层(译者注,#include<prj_name/public/tools.h>

5) 除非万不得已,否则不使用任何不标准的扩展,如#pragma__declspec,允许使用__declspec(dllimport)__declspec(dllexport),但必须通过DLLIMPORTDLLEXPORT等宏,以便其他人在共享使用这些代码时容易放弃这些扩展。

在Windows上,只有很少一些偶尔可以不遵守的规则:

1) 通常我们禁止使用多重继承,但在使用COMATL/WTL类时可以使用多重继承,为了执行COMATL/WTL类及其接口时可以使用多重实现继承;

2) 虽然代码中不应使用异常,但在ATL和部分STL(包括Visual C++的STL)中异常被广泛使用,使用ATL时,应定义_ATL_NO_EXCEPTIONS以屏蔽异常,你要研究一下是否也屏蔽掉STL的异常,如果不屏蔽,开启编译器异常也可以,注意这只是为了编译STL,自己仍然不要写含异常处理的代码;

3) 通常每个项目的每个源文件中都包含一个名为StdAfx.hprecompile.h的头文件方便头文件预编译,为了使代码方便与其他项目共享,避免显式包含此文件(precompile.cc除外),使用编译器选项/FI以自动包含;

4) 通常名为resource.h、且只包含宏的资源头文件,不必拘泥于此风格指南。

  • 团队合作

参考常识,保持一致

编辑代码时,花点时间看看项目中的其他代码并确定其风格,如果其他代码if语句中使用空格,那么你也要使用。如果其中的注释用星号(*)围成一个盒子状,你也这样做:

/**********************************
* Some comments are here.
* There may be many lines.
**********************************/

编程风格指南的使用要点在于提供一个公共的编码规范,所有人可以把精力集中在实现内容而不是表现形式上。我们给出了全局的风格规范,但局部的风格也很重要,如果你在一个文件中新加的代码和原有代码风格相去甚远的话,这就破坏了文件本身的整体美观也影响阅读,所以要尽量避免。

好了,关于编码风格写的差不多了,代码本身才是更有趣的,尽情享受吧!

Benjy Weinberger
Craig Silverstein
Gregory Eitzmann
Mark Mentovai
Tashana Landray

______________________________________

译者:终于翻完了,前后历时两周,整个过程中,虽因工作关系偶有懈怠,但总算不是虎头蛇尾(起码我的态度是非常认真的:D),无论是否能对你有所裨益,对我而言,至少是温习了一些以前知道的知识,也学到了一些之前不知道的知识

刚好这两天还不是特紧张,赶紧翻完了,要开始干活了……

posted @ 2008-07-23 14:28 Fox 阅读(3803) | 评论 (11)编辑 收藏

原文地址:

  • 格式

代码风格和格式确实比较随意,但一个项目中所有人遵循同一风格是非常容易的,作为个人未必同意下述格式规则的每一处,但整个项目服从统一的编程风格是很重要的,这样做才能让所有人在阅读和理解代码时更加容易。

1. 行长度(Line Length)

每一行代码字符数不超过80。

我们也认识到这条规则是存有争议的,但如此多的代码都遵照这一规则,我们感觉一致性更重要。

优点:提倡该原则的人认为强迫他们调整编辑器窗口大小很野蛮。很多人同时并排开几个窗口,根本没有多余空间拓宽某个窗口,人们将窗口最大尺寸加以限定,一致使用80列宽,为什么要改变呢?

缺点:反对该原则的人则认为更宽的代码行更易阅读,80列的限制是上个世纪60年代的大型机的古板缺陷;现代设备具有更宽的显示屏,很轻松的可以显示更多代码。

结论:80个字符是最大值。例外:

1) 如果一行注释包含了超过80字符的命令或URL,出于复制粘贴的方便可以超过80字符;

2) 包含长路径的可以超出80列,尽量避免;

3) 头文件保护(防止重复包含第一篇)可以无视该原则。

2. 非ASCII字符(Non-ASCII Characters)

尽量不使用非ASCII字符,使用时必须使用UTF-8格式。

哪怕是英文,也不应将用户界面的文本硬编码到源代码中,因此非ASCII字符要少用。特殊情况下可以适当包含此类字符,如,代码分析外部数据文件时,可以适当硬编码数据文件中作为分隔符的非ASCII字符串;更常用的是(不需要本地化的)单元测试代码可能包含非ASCII字符串。此类情况下,应使用UTF-8格式,因为很多工具都可以理解和处理其编码,十六进制编码也可以,尤其是在增强可读性的情况下——如"\xEF\xBB\xBF"是Unicode的zero-width no-break space字符,以UTF-8格式包含在源文件中是不可见的。

3. 空格还是制表位(Spaces vs. Tabs)

只使用空格,每次缩进2个空格。

使用空格进行缩进,不要在代码中使用tabs,设定编辑器将tab转为空格。

译者注:在前段时间的关于Debian开发学习日记一文中,曾给出针对C/C++编码使用的vim配置。

4. 函数声明与定义(Function Declarations and Definitions)

返回类型和函数名在同一行,合适的话,参数也放在同一行。

函数看上去像这样:

ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
  DoSomething();
  ...
}

如果同一行文本较多,容不下所有参数:

ReturnType ClassName::ReallyLongFunctionName(Type par_name1,
                                             Type par_name2,
                                             Type par_name3) {
  DoSomething();
  ...
}

甚至连第一个参数都放不下:

ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  // 4 space indent
    Type par_name2,
    Type par_name3) {
  DoSomething();  // 2 space indent
  ...
}

注意以下几点:

1) 返回值总是和函数名在同一行;

2) 左圆括号(open parenthesis)总是和函数名在同一行;

3) 函数名和左圆括号间没有空格;

4) 圆括号与参数间没有空格;

5) 左大括号(open curly brace)总在最后一个参数同一行的末尾处;

6) 右大括号(close curly brace)总是单独位于函数最后一行;

7) 右圆括号(close parenthesis)和左大括号间总是有一个空格;

8) 函数声明和实现处的所有形参名称必须保持一致;

9) 所有形参应尽可能对齐;

10) 缺省缩进为2个空格;

11) 独立封装的参数保持4个空格的缩进。

如果函数为const的,关键字const应与最后一个参数位于同一行。

// Everything in this function signature fits on a single line
ReturnType FunctionName(Type par) const {
  ...
}

// This function signature requires multiple lines, but
// the const keyword is on the line with the last parameter.
ReturnType ReallyLongFunctionName(Type par1,
                                  Type par2) const {
  ...
}

如果有些参数没有用到,在函数定义处将参数名注释起来:

// Always have named parameters in interfaces.
class Shape {
 public:
  virtual void Rotate(double radians) = 0;
}

// Always have named parameters in the declaration.
class Circle : public Shape {
 public:
  virtual void Rotate(double radians);
}

// Comment out unused named parameters in definitions.
void Circle::Rotate(double /*radians*/) {}
// Bad - if someone wants to implement later, it's not clear what the
// variable means.
void Circle::Rotate(double) {}

译者注:关于UNIX/Linux风格为什么要把左大括号置于行尾(.cc文件的函数实现处,左大括号位于行首),我的理解是代码看上去比较简约,想想行首除了函数体被一对大括号封在一起之外,只有右大括号的代码看上去确实也舒服;Windows风格将左大括号置于行首的优点是匹配情况一目了然。

5. 函数调用(Function Calls)

尽量放在同一行,否则,将实参封装在圆括号中。

函数调用遵循如下形式:

bool retval = DoSomething(argument1, argument2, argument3);

如果同一行放不下,可断为多行,后面每一行都和第一个实参对齐,左圆括号后和右圆括号前不要留空格:

bool retval = DoSomething(averyveryveryverylongargument1,
                          argument2, argument3);

如果函数参数比较多,可以出于可读性的考虑每行只放一个参数:

bool retval = DoSomething(argument1,
                          argument2,
                          argument3,
                          argument4);

如果函数名太长,以至于超过行最大长度,可以将所有参数独立成行:

if (...) {
  ...
  ...
  if (...) {
    DoSomethingThatRequiresALongFunctionName(
        very_long_argument1,  // 4 space indent
        argument2,
        argument3,
        argument4);
  }

6. 条件语句(Conditionals)

更提倡不在圆括号中添加空格,关键字else另起一行。

对基本条件语句有两种可以接受的格式,一种在圆括号和条件之间有空格,一种没有。

最常见的是没有空格的格式,那种都可以,还是一致性为主。如果你是在修改一个文件,参考当前已有格式;如果是写新的代码,参考目录下或项目中其他文件的格式,还在徘徊的话,就不要加空格了。

if (condition) {  // no spaces inside parentheses
  ...  // 2 space indent.
} else {  // The else goes on the same line as the closing brace.
  ...
}

如果你倾向于在圆括号内部加空格:

if ( condition ) {  // spaces inside parentheses - rare
  ...  // 2 space indent.
} else {  // The else goes on the same line as the closing brace.
  ...
}

注意所有情况下if和左圆括号间有个空格,右圆括号和左大括号(如果使用的话)间也要有个空格:

if(condition)     // Bad - space missing after IF.
if (condition){   // Bad - space missing before {.
if(condition){    // Doubly bad.
if (condition) {  // Good - proper space after IF and before {.

有些条件语句写在同一行以增强可读性,只有当语句简单并且没有使用else子句时使用:

if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();

如果语句有else分支是不允许的:

// Not allowed - IF statement on one line when there is an ELSE clause
if (x) DoThis();
else DoThat();

通常,单行语句不需要使用大括号,如果你喜欢也无可厚非,也有人要求if必须使用大括号:

if (condition)
  DoSomething();  // 2 space indent.

if (condition) {
  DoSomething();  // 2 space indent.
}

但如果语句中哪一分支使用了大括号的话,其他部分也必须使用:

// Not allowed - curly on IF but not ELSE
if (condition) {
  foo;
} else
  bar;

// Not allowed - curly on ELSE but not IF
if (condition)
  foo;
else {
  bar;
}
 
// Curly braces around both IF and ELSE required because
// one of the clauses used braces.
if (condition) {
  foo;
} else {
  bar;
}

7. 循环和开关选择语句(Loops and Switch Statements)

switch语句可以使用大括号分块;空循环体应使用{}continue

switch语句中的case块可以使用大括号也可以不用,取决于你的喜好,使用时要依下文所述。

如果有不满足case枚举条件的值,要总是包含一个default(如果有输入值没有case去处理,编译器将报警)。如果default永不会执行,可以简单的使用assert

switch (var) {
  case 0: {  // 2 space indent
    ...      // 4 space indent
    break;
  }
  case 1: {
    ...
    break;
  }
  default: {
    assert(false);
  }
}

空循环体应使用{}continue,而不是一个简单的分号:

while (condition) {
  // Repeat test until it returns false.
}
for (int i = 0; i < kSomeNumber; ++i) {}  // Good - empty body.
while (condition) continue;  // Good - continue indicates no logic.
while (condition);  // Bad - looks like part of do/while loop.

8. 指针和引用表达式(Pointers and Reference Expressions)

句点(.)或箭头(->)前后不要有空格,指针/地址操作符(*、&)后不要有空格。

下面是指针和引用表达式的正确范例:

x = *p;
p = &x;
x = r.y;
x = r->y;

注意:

1) 在访问成员时,句点或箭头前后没有空格;

2) 指针操作符*&后没有空格。

在声明指针变量或参数时,星号与类型或变量名紧挨都可以:

// These are fine, space preceding.
char *c;
const string &str;

// These are fine, space following.
char* c;    // but remember to do "char* c, *d, *e, ...;"!
const string& str;
char * c;  // Bad - spaces on both sides of *
const string & str;  // Bad - spaces on both sides of &

同一个文件(新建或现有)中起码要保持一致。

译者注:个人比较习惯与变量紧挨的方式

9. 布尔表达式(Boolean Expressions)

如果一个布尔表达式超过标准行宽(80字符),如果断行要统一一下。

下例中,逻辑与(&&)操作符总位于行尾:

if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another & last_one) {
  ...
}

两个逻辑与(&&)操作符都位于行尾,可以考虑额外插入圆括号,合理使用的话对增强可读性是很有帮助的。

译者注:个人比较习惯逻辑运算符位于行首,逻辑关系一目了然,各人喜好而已,至于加不加圆括号的问题,如果你对优先级了然于胸的话可以不加,但可读性总是差了些

10. 函数返回值(Return Values)

return表达式中不要使用圆括号。

函数返回时不要使用圆括号:

return x;  // not return(x);

11. 变量及数组初始化(Variable and Array Initialization)

选择=还是()

需要做二者之间做出选择,下面的形式都是正确的:

int x = 3;
int x(3);
string name("Some Name");
string name = "Some Name";

12. 预处理指令(Preprocessor Directives)

预处理指令不要缩进,从行首开始。

即使预处理指令位于缩进代码块中,指令也应从行首开始。

// Good - directives at beginning of line
  if (lopsided_score) {
#if DISASTER_PENDING      // Correct -- Starts at beginning of line
    DropEverything();
#endif
    BackToNormal();
  }
// Bad - indented directives
  if (lopsided_score) {
    #if DISASTER_PENDING  // Wrong!  The "#if" should be at beginning of line
    DropEverything();
    #endif                // Wrong!  Do not indent "#endif"
    BackToNormal();
  }

13. 类格式(Class Format)

声明属性依次序是public:protected:private:,每次缩进1个空格(译者注,为什么不是两个呢?也有人提倡private在前,对于声明了哪些数据成员一目了然,还有人提倡依逻辑关系将变量与操作放在一起,都有道理:-)

类声明(对类注释不了解的话,参考第六篇中的类注释一节)的基本格式如下:

class MyClass : public OtherClass {
 public:      // Note the 1 space indent!
  MyClass();  // Regular 2 space indent.
  explicit MyClass(int var);
  ~MyClass() {}

  void SomeFunction();
  void SomeFunctionThatDoesNothing() {
  }

  void set_some_var(int var) { some_var_ = var; }
  int some_var() const { return some_var_; }

 private:
  bool SomeInternalFunction();

  int some_var_;
  int some_other_var_;
  DISALLOW_COPY_AND_ASSIGN(MyClass);
};

注意:

1) 所以基类名应在80列限制下尽量与子类名放在同一行;

2) 关键词public:、protected:private:要缩进1个空格(译者注,MSVC多使用tab缩进,且这三个关键词没有缩进)

3) 除第一个关键词(一般是public)外,其他关键词前空一行,如果类比较小的话也可以不空;

4) 这些关键词后不要空行;

5) public放在最前面,然后是protectedprivate

6) 关于声明次序参考第三篇声明次序一节。

14. 初始化列表(Initializer Lists)

构造函数初始化列表放在同一行或按四格缩进并排几行。

两种可以接受的初始化列表格式:

// When it all fits on one line:
MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) {

// When it requires multiple lines, indent 4 spaces, putting the colon on
// the first initializer line:
MyClass::MyClass(int var)
    : some_var_(var),             // 4 space indent
      some_other_var_(var + 1) {  // lined up
  ...
  DoSomething();
  ...
}

15. 命名空间格式化(Namespace Formatting)

命名空间内容不缩进。

命名空间不添加额外缩进层次,例如:

namespace {

void foo() {  // Correct.  No extra indentation within namespace.
  ...
}

}  // namespace

不要缩进:

namespace {

  // Wrong.  Indented when it should not be.
  void foo() {
    ...
  }

}  // namespace

16. 水平留白(Horizontal Whitespace)

水平留白的使用因地制宜。不要在行尾添加无谓的留白。

普通

void f(bool b) {  // Open braces should always have a space before them.
  ...
int i = 0;  // Semicolons usually have no space before them.
int x[] = { 0 };  // Spaces inside braces for array initialization are
int x[] = {0};    // optional.  If you use them, put them on both sides!
// Spaces around the colon in inheritance and initializer lists.
class Foo : public Bar {
 public:
  // For inline function implementations, put spaces between the braces
  // and the implementation itself.
  Foo(int b) : Bar(), baz_(b) {}  // No spaces inside empty braces.
  void Reset() { baz_ = 0; }  // Spaces separating braces from implementation.
  ...

添加冗余的留白会给其他人编辑时造成额外负担,因此,不要加入多余的空格。如果确定一行代码已经修改完毕,将多余的空格去掉;或者在专门清理空格时去掉(确信没有其他人在使用)。

循环和条件语句

if (b) {          // Space after the keyword in conditions and loops.
} else {          // Spaces around else.
}
while (test) {}   // There is usually no space inside parentheses.
switch (i) {
for (int i = 0; i < 5; ++i) {
switch ( i ) {    // Loops and conditions may have spaces inside
if ( test ) {     // parentheses, but this is rare.  Be consistent.
for ( int i = 0; i < 5; ++i ) {
for ( ; i < 5 ; ++i) {  // For loops always have a space after the
  ...                   // semicolon, and may have a space before the
                        // semicolon.
switch (i) {
  case 1:         // No space before colon in a switch case.
    ...
  case 2: break;  // Use a space after a colon if there's code after it.

操作符

x = 0;              // Assignment operators always have spaces around
                    // them.
x = -5;             // No spaces separating unary operators and their
++x;                // arguments.
if (x && !y)
  ...
v = w * x + y / z;  // Binary operators usually have spaces around them,
v = w*x + y/z;      // but it's okay to remove spaces around factors.
v = w * (x + z);    // Parentheses should have no spaces inside them.

模板和转换

vector<string> x;           // No spaces inside the angle
y = static_cast<char*>(x);  // brackets (< and >), before
                            // <, or between >( in a cast.
vector<char *> x;           // Spaces between type and pointer are
                            // okay, but be consistent.
set<list<string> > x;       // C++ requires a space in > >.
set< list<string> > x;      // You may optionally make use
                            // symmetric spacing in < <.

17. 垂直留白(Vertical Whitespace)

垂直留白越少越好。

这不仅仅是规则而是原则问题了:不是非常有必要的话就不要使用空行。尤其是:不要在两个函数定义之间空超过2行,函数体头、尾不要有空行,函数体中也不要随意添加空行。

基本原则是:同一屏可以显示越多的代码,程序的控制流就越容易理解。当然,过于密集的代码块和过于疏松的代码块同样难看,取决于你的判断,但通常是越少越好。

函数头、尾不要有空行:

void Function() {

  // Unnecessary blank lines before and after

}

代码块头、尾不要有空行:

while (condition) {
  // Unnecessary blank line after

}
if (condition) {

  // Unnecessary blank line before
}

if-else块之间空一行还可以接受:

if (condition) {
  // Some lines of code too small to move to another function,
  // followed by a blank line.

} else {
  // Another block of code
}

______________________________________

译者:首先说明,对于代码格式,因人、因系统各有优缺点,但同一个项目中遵循同一标准还是有必要的:

1. 行宽原则上不超过80列,把22寸的显示屏都占完,怎么也说不过去;

2. 尽量不使用非ASCII字符,如果使用的话,参考UTF-8格式(尤其是UNIX/Linux下,Windows下可以考虑宽字符),尽量不将字符串常量耦合到代码中,比如独立出资源文件,这不仅仅是风格问题了;

3. UNIX/Linux下无条件使用空格,MSVC的话使用Tab也无可厚非;

4. 函数参数、逻辑条件、初始化列表:要么所有参数和函数名放在同一行,要么所有参数并排分行;

5. 除函数定义的左大括号可以置于行首外,包括函数/类/结构体/枚举声明、各种语句的左大括号置于行尾,所有右大括号独立成行;

6. ./->操作符前后不留空格,*/&不要前后都留,一个就可,靠左靠右依各人喜好;

7. 预处理指令/命名空间不使用额外缩进,类/结构体/枚举/函数/语句使用缩进;

8. 初始化用=还是()依个人喜好,统一就好;

9. return不要加();

10. 水平/垂直留白不要滥用,怎么易读怎么来。

posted @ 2008-07-23 11:43 Fox 阅读(4347) | 评论 (7)编辑 收藏

仅列出标题
共7页: 1 2 3 4 5 6 7