Subscribe: C++博客-游戏人生-随笔分类-T技术碎语
http://www.cppblog.com/Fox/category/6273.html/rss
Added By: Feedage Forager Feedage Grade B rated
Language: Chinese simplified
Tags:
Rate this Feed
Rate this feedRate this feedRate this feedRate this feedRate this feed
Rate this feed 1 starRate this feed 2 starRate this feed 3 starRate this feed 4 starRate this feed 5 star

Comments (0)

Feed Details and Statistics Feed Statistics
Preview: C++博客-游戏人生-随笔分类-T技术碎语

C++博客-游戏人生-随笔分类-T技术碎语



游戏人生 != ( 人生 == 游戏 ) 站点迁移至:http://www.yulefox.com。请订阅本博的朋友将RSS修改为http://feeds.feedburner.com/yulefox



Published: Wed, 06 Jan 2010 21:30:37 GMT

Last Build Date: Wed, 06 Jan 2010 21:30:37 GMT

 



automake使用小记

Wed, 06 Jan 2010 17:32:00 GMT

本文同步自游戏人生

最近有点忙,本来要用autoconf+automake把自己的代码梳理一下的,因为工作停了近两周。

本想看看有什么工具可以自动生成Makefile.am,答案是:Automake不支持通配符,而且还口口声声,振振有词。既然说的这么言词凿凿,情深意切,我想我也没有必要用shell生成Makefile.am了。

用着用着,我有点怀疑人生了:不知道什么时候需要用autoconf和automake。如果我只是平时自己写一些toy codes的话,感觉用autoconf和automake有点大炮打蚊子的感觉,而且每次新加代码或者是移除代码、甚至是更改目录,都要重新执行 autoconf、automake(不知道我说的对与不对)。对于一个大型项目,执行一次configure和make是很痛苦的一件事,make的中 间目标文件或者库文件、执行文件倒是不一定非得完全rebuild,configure的配置检查呢?是不是也有类似机制?反正我在用ogre或者 cegui的时候,每次执行./configure是重新配置了的。

实际在开源项目里面也不可能维护两套makefile吧。

看了一下googletest的配置,倒是清爽的很,最大的特点是只有一个Makefile.am,这样在一个项目里面只需要维护一个Makefile.am就够了。

cegui比较常规,每个子目录都会维护一个Makefile.am。

需要特别注意的是ogre从1.7.0开始已经开始使用cmake了……

请听题:管理中小型项目,你倾向于下面哪个工具?

o make:钻木取火,玩的就是个技术,编译代码,只用装B的,不用牛B的,你要是用什么cmake,你都不好意思跟别人打招呼,这么经典的东西,精通需要多久?要我说怎么着也得个把俩月吧,个把俩月?那是入门,至少半年,就这还得有Feldman的悟性,不舍昼夜;

o autoconf+automake:既有群众基础,又有技术含量,你是那样拉轰的男人,不管在什么地方,就好像漆黑中的萤火虫一样,那样的鲜明,那样的 出众。你那忧郁的眼神,稀嘘的胡喳子,神乎其技的指法;既可以耻笑原始人的生产力低下,还可以鄙视现代人的不学无术。

o cmake:在MSVCers面前抬不起头,在UNIXers面前似乎更抬不起头;而cmake对WINDOWS和UNIX平台的完美支持,足以让所有的 MSVCers和UNIXer在你面前抬不起头,你是公鸡中的战斗机。所以你还是可以趾高气昂的丢下一句:走NB的路,让SB说去吧。


(image)

Fox 2010-01-07 01:32 发表评论



Autotools初体验

Tue, 22 Dec 2009 18:18:00 GMT

     摘要: 从接触和使用make以来,前前后后写了不少Makefile(添添减减、修修补补,累计上千行是有的),今天在重新整理代码的组织结构之后,突然就想:我为什么不使用Autotools呢?

在开始体验功能强大的Autotools之前,简单(详细)回忆总结一下我使用make的经历和思考的过程,反省一下看看自己在接触这些新鲜事物的时候到底走了多少弯路。  阅读全文(image)

Fox 2009-12-23 02:18 发表评论



ACE vs Boost: Singleton的实现

Mon, 21 Sep 2009 16:38:00 GMT

本文同步自游戏人生 以前曾经讨论过Singleton的实现,这次在对照ACE和Boost代码的时候,又重新审视了一下二者对Singleton不同的实现。其间的差别也体现了不同的编程哲学:ACE的实现更加偏重多线程中的安全和效率问题;Boost的实现则偏重于使用语言自身的特性满足Singleton模式的基本需求。 o ACE的实现 Douglas C. Schmidt在Double-Checked Locking: An Optimization Pattern for Efficiently Initializing and Accessing Thread-safe Objects一文中对double-check lock(一般译为双检锁)进行了详细的阐述。 ACE的Singleton使用Adapter模式实现对其他类的适配,使之具有全局唯一的实例。由于C++标准并非明确指定全局静态对象的初始化顺序,ACE使用double-check lock保证线程安全,并使之不受全局静态对象初始化顺序的影响,同时也避免了全局静态实现方式的初始化后不使用的开销。 如果你能够准确的区分以下三种实现的弊端和隐患,对double-check lock也就有了足够的了解。 // -------------------------------------------class Singleton{public:    static Singleton *instance (void)    {        // Constructor of guard acquires        // lock_ automatically.        Guard guard (lock_);        // Only one thread in the        // critical section at a time.        if (instance_ == 0)            instance_ = new Singleton;        return instance_;        // Destructor of guard releases        // lock_ automatically.    }private:    static Mutex lock_;    static Singleton *instance_;}; // ---------------------------------------------static Singleton *instance (void){    if (instance_ == 0) {        Guard guard (lock_);        // Only come here if instance_        // hasn’t been initialized yet.        instance_ = new Singleton;    }    return instance_;} // ---------------------------------------------class Singleton{public:    static Singleton *instance (void)    {        // First check        if (instance_ == 0)        {            // Ensure serialization (guard            // constructor acquires lock_).            Guard guard (lock_);            // Double check.            if (instance_ == 0)                instance_ = new Singleton;        }        return instance_;        // guard destructor releases lock_.    }private:    static Mutex lock_;    static Singleton *instance_;}; 更多详情,见Schmidt老师的原文和ACE_Singleton实现。 o Boost的实现 Boost的Singleton也是线程安全的,而且没有使用锁机制。当然,Boost的Singleton有以下限制(遵从这些限制,可以提高效率): o The classes below support usage of singletons, including use in pr[...]



IOCP使用时常见的几个错误

Fri, 11 Sep 2009 16:20:00 GMT

本文同步自游戏人生 在使用IOCP时,最重要的几个API就是GetQueueCompeltionStatus、WSARecv、WSASend,数据的I/O及其完成状态通过这几个接口获取并进行后续处理。 GetQueueCompeltionStatus attempts to dequeue an I/O completion packet from the specified I/O completion port. If there is no completion packet queued, the function waits for a pending I/O operation associated with the completion port to complete.BOOL WINAPI GetQueuedCompletionStatus( __in HANDLE CompletionPort, __out LPDWORD lpNumberOfBytes, __out PULONG_PTR lpCompletionKey, __out LPOVERLAPPED *lpOverlapped, __in DWORD dwMilliseconds ); If the function dequeues a completion packet for a successful I/O operation from the completion port, the return value is nonzero. The function stores information in the variables pointed to by the lpNumberOfBytes, lpCompletionKey, and lpOverlapped parameters. 除了关心这个API的in & out(这是MSDN开头的几行就可以告诉我们的)之外,我们更加关心不同的return & out意味着什么,因为由于各种已知或未知的原因,我们的程序并不总是有正确的return & out。 If *lpOverlapped is NULL and the function does not dequeue a completion packet from the completion port, the return value is zero. The function does not store information in the variables pointed to by the lpNumberOfBytes and lpCompletionKey parameters. To get extended error information, call GetLastError. If the function did not dequeue a completion packet because the wait timed out, GetLastError returns WAIT_TIMEOUT. 假设我们指定dwMilliseconds为INFINITE。 这里常见的几个错误有: WSA_OPERATION_ABORTED (995): Overlapped operation aborted. 由于线程退出或应用程序请求,已放弃I/O 操作。 MSDN: An overlapped operation was canceled due to the closure of the socket, or the execution of the SIO_FLUSH command in WSAIoctl. Note that this error is returned by the operating system, so the error number may change in future releases of Windows. 成因分析:这个错误一般是由于peer socket被closesocket或者WSACleanup关闭后,针对这些socket的pending overlapped I/O operation被中止。 解决方案:针对socket,一般应该先调用shutdown禁止I/O操作后再调用closesocket关闭。 严重程度:轻微易处理。 WSAENOTSOCK (10038): Socket operation on nonsocket. MSDN: An operation was attempted on something that is not a socket. Either the socket handle parameter did not reference a valid socket, or for select, a member of an fd_set was not valid. 成因分析:在一个非套接字上尝试了一个操作。 使用closesocket关闭socket之后,针对该invalid socket的任何操作都会获得该错误。 解决方案:如果是多线程存在对同一socket的操作,要保证对socket的I/O操作逻辑上的顺序,做好socket的graceful disconnect。 严重程度:轻微易处理。 WSAECONNRESET (10054): Connection reset by peer. 远程主机强迫关闭了一个现有的连接。 MSDN: An existing connection was forcibly closed by the remote host. This normally results if the peer application on the remote host is suddenly stopped, the host is rebooted, the host or remote network interface is disabled, or the remote host uses a hard close (see setsockopt for more information on the SO_LINGER option on the remote socket). This error may also result if a connection was broken due to keep-alive activity detecting a failure while one or more operations are in progress. Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET. 成因分析:在使用WSAAccpet、WSARecv、WSASend等接口时,如果peer application突然中止(原因如上所述),往其对应的socket上投递的operations将会失败。 解决方案:如果是对方主机或程序意外中止,那就只有各安天命了。但如果这程序是你写的,而你只[...]



Amdahl 定律 = Gustafson定律

Thu, 10 Sep 2009 04:04:00 GMT

本文同步自游戏人生

周伟明老师应该是多核计算领域的老人了。

这几日因为想找找无锁(lock-free)方面的信息,就打开了周老师的blog。看到多核系统中三种典型锁竞争的加速比分析这篇文章时,觉得老师强调多核计算效率是有必要的,但拿Amdahl 定律和Gustafson定律作对比有点不恰当。

按照我的理解,这两个定律所刻画的内容是完全一致的,只是对加速比的定义不一样罢了。这里,我们都以S(n)表示n核系统对具体程序的加速比,K表示串行部分计算时间比例。

Amdahl 定律的加速比:S(n) = 使用1个处理器的串行计算时间 / 使用n个处理器的并行计算时间

S(n) = 1/(K+(1-K)/n) = n/(1+(n-1)K)

Gustafson定律的加速比:S(n) = 使用n个处理器的并行计算量 / 使用1个处理器的串行计算量

S(n) = K+(1-K)n

通俗的讲,Amdahl 定律将工作量看作1,有n核也只能分担1-K的工作量;而Gustafson定律则将单核工作量看作1,有n核,就可以增加n(1-K)的工作量。

这两个计算公式都没有将锁开销考虑在内,是理想化的。周老师提到设计不当造成并行变串行的问题与这两个公式计算无关。因为任何多核计算都存在对串行和并行的设计考量,这正是程序员在使用多核并行时最关心的事情。

总之,二者的区别只在于态度的不同:一个消极悲观,一个积极乐观,充其量是一个冷笑话,而于多核计算没有任何关联。

我说这些也与多核计算没有关联,丝毫没有质疑多核效率的意思。相反,我期待能够通过技术层面提高多核的有效负载。

最后一句题外话,周老师使用Word的水平一般:所有来自Word的截图都是在页面视图直接截,换行符和光标随处可见。(image)

Fox 2009-09-10 12:04 发表评论



ACE: Socket封装(01)

Tue, 01 Sep 2009 06:22:00 GMT

本文同步自游戏人生 o *__ 序 __* o 在阅读ACE代码和C++NPv1, v2, APG的时候,我意识到一个问题:虽然稍有C++和网络基础的同学都可以读懂ACE,但如果你对OS(五大管理模块都包含在内)、TCP/IP、C++、Design Patterns了解越多,你就越能体会ACE为什么需要这么庞杂,虽然它不够完美(但至少我还没有资格来批评这一点,我现在最常想做的一个动作就是五体投地)。 而且我隐约感觉到,我现在所写的很多东西在以后(对于有些人或许就是现在)看来会相当不深刻、相当不严谨,但对于一段学习历程,这个过程是必然的、必需的。 在C++NPv1中,Douglas C. Schmidt把原始socket及其API的缺陷有些妖魔化了,比如一段加上注释、空行在内的35行的代码,被指出有10处错误之多。这就像很多其他语言的倡导者或反传统C/C++指针者在批评指针时的说法一样。长期使用原始socket和指针的同学对此感觉很不舒服,何况socket API提供了大量错误检测的接口,至多是不够友好罢了。你好就好了,没必要抓住别人一顿痛批吧,『本是同根生,相煎何太急』。 虽然Solaris、Linux的很多版本及Windows对起源于Berkeley的socket API进行了重写,但不可否认,由于历史原因和POSIX标准的存在,对于使用者而言,我们可以无视这些API的实现差异。只是一旦我们从socket通信扩展到其他IPC通信的话,就需要正视各种I/O细节的差异了。 由于UNIX中,对于socket, file, pipe, device的大多数操作,描述符都是通用的(这一点,OS上面讲的更清楚些)。而Windows中,句柄大多不能互换(socket对于MS来说是舶来品)。系统和标准的不一致导致地址、协议和API的混杂甚至混乱。 UNIX下的描述符和Windows的句柄可以看作是同一个概念,只是应用环境不一样,所描述的内容也时常不一样,再简单了说,它们都是一个整型的ID。 ACE的源码中使用了大量预处理指令,尤其在跨平台/编译环境的部分更加明显。鉴于C/C++标准的博大胸怀,有些指令需要阅读相关编译器提供的帮助文档: o #pragma: GCC, MSVC o #define (#, #@, ##) : GCC, MSVC 其中有若干代码文件以.inl为后缀,里面是对部分函数的内联实现,以使代码结构看上去更加简洁。如果确定使用内联函数的话,*.inl将被包含于*.h的最后,如果不使用,则像*.h一样,包含于*.cpp的头部。 ACE采用doxygen输出文档,在阅读代码注释时能够感受到差异,但基本不会影响阅读。 o * __ 关于第3章(C++NPv1)__ * o ACE抽象的地址类ACE_Addr拥有ACE_DEV_Addr, ACE_FILE_Addr, ACE_INET_Addr, ACE_SPIPE_Addr, ACE_UNIX_Addr五个子类。对于狭义上的网络通信(TCP/IP)而言,ACE_INET_Addr对应于我们熟悉的sockaddr_in。 ACE_IPC_SAP是IPC(interprocess communication)I/O操作类的root类。 从编码的角度看,这个类漂亮的地方在于示例了抽象类的另一种实现方式。 一提到抽象类,大多数人的第一反应是pure virtual function。当一个基类确定需要使用virtual function时,这是一个不错的选择。但我们都知道虚拟函数有开销。而且对于一个结构简单的抽象基类和其继承子类(尤其是大量使用时),一个虚函数表带来的开销会让整个设计显得十分蹩脚。 我们都知道如何强制让一个类无法使用default constructor(protected)。如果对基类使用该方法,仅使子类具有public的default constructor,这就达到了定义抽象基类的效果。 virtual destructor的意义在于防止delete父类指针(指向子类对象)时未调用子类destructor。在此例中,为避免这种情况,同样将destructor声明为protected即可。 从设计实现的角度看,相较于socket[...]



Doxygen在Cygwin下的使用

Fri, 28 Aug 2009 09:14:00 GMT

本文同步自游戏人生

我发现我最近成了Cygwin下的小白鼠,写完Cygwin下安装ACE,写ACE在cygwin下的使用。现在又写doxygen

之前提到在Cygwin下读代码的不习惯,后来回到VS下看。没过几天,觉得VS下还是不够直观,于是就直接看ACE的doxygen了……

doxygen好是好,用起来还是要慢慢习惯才行,需要在写注释和代码的时候注意一些,掌握的细节和技巧越多,出来的文档越丰富(当然,这和代码质量是两码事)。

我自然是把doxygen安装在Cygwin下了,由于doxygen没有提供info,Info doxygen时就自动打开了doxygen的man,和man doxygen、doxygen --help一个效果。

如果希望阅读更详尽的使用方法,只有自己down一个manual了

在Cygwin下,doxygen采用GNU的libiconv进行文字编码的转换,以UTF-8作为默认编码。

使用doxygen生成config-file模板后,可以在config-file中进行一些项目设置(有注释的,看的懂)。

为了支持中文,我DOXYFILE_ENCODING用的是EUC-CN,但输出文档的语言OUTPUT_LANGUAGE却选了English。两点原因:

o EUC-CN(各种汉字编码知识就不在此普及了,你可以认为简体字编码都是EUC-CN)和UTF-8不同,但OUTPUT_LANGUAGE的各种语言都是使用的UTF-8,所以两种编码不可能同时显示,当然,你可以把EUC-CN全转成UTF-8。编码不是高级的技术,但对于非英语用户绝对是一个噩梦后来发现是我自己学艺不精,DOXYFILE_ENCODING只是配置文件的编码格式而已,而识别中文文档只需要修改INPUT_ENCODING成EUC-CN即可,OUTPUT_LANGUAGE自然设置成Chinese也不会有问题,因为doxygen采用UTF-8输出,使用中文输出不会有乱码问题

o 虽然我的英文很蹩脚,虽然我的文档中多有中文注释。但像doxygen中文输出的文档中把class、public都给你翻译成中文,你也受不了,这也英文水平无关。

config-file中的其他内容我现在也用不到,就没有仔细看。

因为Kevin会在公司里讲一下doxygen,doxygen的manual也讲的很详细,我就省点时间,不翻译文档了。

简单的一个Doxygen的测试在这里。

(image)

Fox 2009-08-28 17:14 发表评论



ACE之Cygwin vs Win

Mon, 24 Aug 2009 03:59:00 GMT

本文同步自游戏人生

我屈服了,还是VS用的方便。

之前在Cygwin下已经可以使用的ACE,因为阅读代码太不方便(对于一个WinEr来说),上午在VS下面花了几分钟就把ACE配好了,而且使用$(ACE_ROOT)\examples\C++NPv1的代码跟踪调试,太习惯了。

按照$(ACE_ROOT)\ACE-INSTALL.html的安装说明:

o 选择并打开$(ACE_ROOT)\ace\ace_vc9.sln

o 添加config.h并加入以下内容:

    #define ACE_HAS_STANDARD_CPP_LIBRARY 1
    #include "ace/config-win32.h"

o F7

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

OK,现在$(ACE_ROOT)\lib下面已经生成了ACEd.dll、ACEd.lib,再设置一下系统环境变量(运行程序必需)和VC++目录(调试程序必需)。可以使用了:

o 选择并打开$(ACE_ROOT)\examples\C++NPv1

o F7

o for (; ; ) { F12, F9, F5, F10, F11 }

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

半个小时就搞定了当时一个星期的折腾……

结论:对于一个不忠实的Win Coder,在MinGW, Cygwin, UNIX…下面装B是要付出代价的。

当然,家里的机器就让它还一直跑Cygwin吧。(image)

Fox 2009-08-24 11:59 发表评论



ACE在cygwin下的使用

Wed, 19 Aug 2009 02:05:00 GMT

本文同步自游戏人生

/*--------- Hello.cc ---------*/

/** Hello.cc:
* @File:   Hello.cc
* @Author: Fox
* @Date:   Aug. 19th, 2009
* @Brief:  Test ACE log module application
*/

#define ACE_NTRACE 0            /// trace the calling position

#include "ace/Log_Msg.h"        /// include log module

int ACE_TMAIN(int, ACE_TCHAR *[])
{
     ACE_TRACE(ACE_TEXT("main"));

     ACE_DEBUG((LM_INFO, ACE_TEXT("%IStart\n")));
     ACE_DEBUG((LM_INFO, ACE_TEXT("%IEnd\n")));

     return 0;
}

/*--------- makefile ---------*/

BIN     = hello                       # src & exe file name
SRC     = $(addsuffix .cc, $(BIN))    # src file suffix
LIBS    = -lACE                       # libACE.dll under cygwin

include $(ACE_ROOT)/include/makeinclude/wrapper_macros.GNU
include $(ACE_ROOT)/include/makeinclude/macros.GNU
include $(ACE_ROOT)/include/makeinclude/rules.common.GNU
include $(ACE_ROOT)/include/makeinclude/rules.nonested.GNU
include $(ACE_ROOT)/include/makeinclude/rules.bin.GNU
include $(ACE_ROOT)/include/makeinclude/rules.local.GNU

/*--------- Compilation ---------*/

GNUmakefile: /home/fox/ace/GNUmakefile MAKEFLAGS=k

g++ -Wpointer-arith -mthreads -mtune=pentiumpro -O3 -g -pipe    -pipe   -I/usr/\
share/ace -DACE_HAS_EXCEPTIONS -DACE_NO_INLINE  -c -o .obj/hello.o hello.cc
g++ -Wpointer-arith -mthreads -mtune=pentiumpro -O3 -g -pipe    -pipe   -I/usr/\
share/ace -DACE_HAS_EXCEPTIONS -DACE_NO_INLINE  -Wl,--enable-auto-import -Wl,-E\
-L/usr/share/ace/lib -o hello .obj/hello.o  -lACE

Compilation finished at Wed Aug 19 00:35:42

/*--------- Result ---------*/

$ ./hello.exe
(14417928) calling main in file `hello.cc' on line 13
    Start
    End
(14417928) leaving main

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

更多内容请参考C++NP(C++ Network Programming) vol.1 & vol.2和APG(The ACE Progrmmer's Guide)

忙活了一晚上,终于知道怎么包含头文件了,在gcc的编译选项中用 -I或/I$(ACE_ROOT):

本例中是:-I/usr/share/ace

结果后面库又链接不上,联想以前使用OpenGL库的LIBS,终于靠一个-lACE搞定。

因为不愿意用MPC,总感觉再多花些时间去弄又只是离题更远了,有兴趣的同学自然是可以通过ACE的官网找到所有问题的答案。

这样一来,ACE在cygwin下从安装到使用也就告一段落了,后面的问题就比较easy了,无非是你用ACE做什么。而我也不会再就ACE && cygwin写什么心得了,总算见证了这两天的捣腾。

(image)

(image)

Fox 2009-08-19 10:05 发表评论



与君共勉

Mon, 17 Aug 2009 08:31:00 GMT

本文同步自游戏人生

现在有些年轻人的心态比较浮躁,大致总结以下『四点表现』:

1. 眼高过顶;

2. 垂手过膝;

3. 期望值高;

4. 积极性低。

深层次的客观原因大致是『四个没有』:

1. 没有吃过苦;

2. 没有干过活;

3. 没有说过话;

4. 没有当过家。

一般都具有『四个特征』:

1. 独生子女;

2. 毕业新人;

3. 沉默寡言;

4. 半瓶开水。

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

1. 你想要什么?

参考:一个有影响力的人。(@李开复

2. 那是否是你想要的?

参考:你的眼光有多远,决定了你能走多远。(@Fox

3. 你需要做什么?

参考:高筑墙,广积粮,缓称王。(朱元璋)

4. 你还需要做什么?

参考:平和的心态,进取的态度,坚定的目标,不懈的努力。(@Fox

5. 你是否做到了?

参考:没有,但我一直在努力。(@Fox

(image)

Fox 2009-08-17 16:31 发表评论



Cygwin下安装ACE

Mon, 17 Aug 2009 01:52:00 GMT

本文同步自游戏人生

-----------------------读书-----------------------

想读《UNIX Network Programming》很久了,只是这种愿望一直没有特别强烈。用的笔记本换了之后强迫自己只是装了cygwin,没有安装VS,平时写些代码就只能在cygwin下用gcc了。最近对UNIX环境编程和网络基础比较感兴趣,于是读这本书终于提上了议事日程,可是大多数书店都没有这本书了。

工作之后,对于自己喜欢的书,就很少会去看电子版,觉得还是捧卷在手的感觉舒服一些,想怎么看怎么看,享受拿笔在上面写写画画的感觉。因此对于想读但买不到的书,第一反应是等待。在翻电脑上面一些资料的时候才反应过来我已经很久没有down过电子书了,结果就从网上down了很多想读(豆瓣)的书,forgive me, Richard Stevens and anybody.

-----------------------动手-----------------------

之前只是在Windows下用过ACE,因为ACE提供了VS各种版本的解决方案,编译过程比较简单。这次放在cygwin下编译的时候,因为环境变量设置问题,并不是非常顺利,参照了源码目录下的ACE-INSTALL.html的描述才将问题解决。现在将过程给出来,仅供google到这里的同学参考:

友情提示:我目前对于cygwin和UNIX类系统的了解停留在知其然(还是在google之后)的水平。

1. 增加环境变量

o 修改/etc/profile文件读写属性:

$ chmod 777 /etc/profile

cygwin下的环境变量可以在该文件中配置,由于该文件默认具有写保护属性,因此需要修改使之可写,修改完成之后应该记得对其进行写保护:

$ chmod 555 /etc/profile

o 打开profile文件(本人使用emacs进行编辑,读者也可以使用vi、vim等,在此不再赘述):

$ emacs /etc/profile

o 往profile中修改和添加环境变量:

将以下五行添加到文件中,ACE_ROOT为ACE源码目录,本文用到的目录是/usr/share/ace:

ACE_ROOT=/usr/share/ace
export ACE_ROOT

LD_LIBRARY_PATH=$ACE_ROOT/ace:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH

# export PATH=$ACE_ROOT/ace:$PATH

这个操作等同于在VS下修改库文件目录,我是直接修改了PATH变量的:

PATH=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:$ACE_ROOT/ace:$PATH

o 执行/etc/profile

$ /etc/profile

执行修改后有可能需要重启cygwin,可使用下面的命令查看ACE_ROOT设置是否正确:

$ echo $ACE_ROOT

2. 创建若干文件:

o 在$ACE_ROOT/ace 文件夹中创建名为 config.h 的文件,并加入以下内容后保存关闭:

#include "ace/config-cygwin32.h"

o 在$ACE_ROOT/include/makeinclude 文件夹中创建名为 platform_macros.GNU 的文件,并加入以下内容保存关闭:

include $(ACE_ROOT)/include/makeinclude/platform_cygwin32.GNU

3. 编译ACE:

$ cd $ACE_ROOT/ace
$ make

大概会花掉十几分钟时间吧。

4. 测试ACE:

$ cd $ACE_ROOT/tests
$ make

大概又会花掉十几分钟时间吧。

接下来就可以使用perl脚本完成所有测试,我没有使用过perl,临时安装了一下。

$ perl run_test.pl

这个脚本在我一位同事的机器上跑了很久(十几分钟 or 几十分钟?),不幸的是,Windows竟然抛出了一个内存读写错误,更加不幸的是,我现在没有能力去确定。

结果周末后面的时间又花在熟悉EMacs上了,还没有看ACE的代码,只有下周才能看了。

-----------------------结束-----------------------

这种小学生的东西拿出来讲确实是很难为情的,然而,于我现在却又算是极大的一个收获。真等我熟悉了其中的很多内容,又未必会再有耐心说了。所以还是记录一下,既是鼓励,也是督促。

晚上睡觉之前翻了一下《ACE程序员指南:网络与系统编程的实用设计模式》,发现里面2.3节也讲到了怎么安装。

(image)

Fox 2009-08-17 09:52 发表评论



设计模式(四)——Template Method

Tue, 10 Feb 2009 17:46:00 GMT

在一个稍微上规模(怎么也有几十个类吧)的C++程序中,继承和组合的使用比比皆是。虽然GoF提出了OOD的两个原则,第二个谓之『优先使用对象组合,而不是继承』,但这绝不意味着不使用继承,正因如此,第一个原则才是『针对接口编程,而不是针对实现编程』,这两个原则清楚的表达了软件工程中『低耦合』的思想。

模板方法(Template Method)的意图正是『定义一个操作中的算法的框架,而将一些步骤延迟到子类中』。

注意,这里有个关键词『一些』,既然是『一些』,就意味着父类的的接口中已经实现了『一些』步骤,否则,父类便只提供了抽象接口。

这一情况是我们在编码时时常遇到的,父类实现了部分算法中的通用行为,子类根据自己的需求可对其进行必要扩充。

class CBase
{
public:
    // Default done
    virtual void Operation(void)
    {
        ...
    }
};

class CDerive :
    public CBase
{
public:
    virtual void Operation(void)
    {
        switch( ... )
        {
        case A: ...
        case B: ...
        case c: ...
        default:
            CBase::Operation();
        }
    }
};

因为对父类的不了解或是对子类的太了解,你以为CDerive::Operation()做了足够多,不需要CBase::Operation(),但实际上仍然需要的情况总在发生。

换一种方式,把Operation()作为模板方法,把DoOperation()作为可扩展的接口提供给子类实现:

class CBase
{
public:
    void SetFocus(void) { ... }
    void ResetFocus(void) { ... }
    // Call by user
    void Operation(void)
    {
        SetFocus();
        DoOperation();
        ResetFocus();
    }
    // Default done
    virtual void DoOperation(void) = 0;
};

class CDerive :
    public CBase
{
public:
    virtual void DoOperation(void)
    {
        ...
    }
};

模板方法只是提供了一个简单的封装技巧,当然不是所有的虚接口都这么写:)。


更多内容请移步我的个人主页

(image)

Fox 2009-02-11 01:46 发表评论



VS2005断点失效的问题

Sun, 04 Jan 2009 03:04:00 GMT

VS2005下使用VC,部分断点无效,显示『当前不会命中断点。还没有为该文档加载任何符号』。

试过以下一些方法:

1、无效断点所在的项目和启动项目的设置:项目->属性->配置属性->C/C++->常规->调试信息格式,这里不能为『禁用』;

2、项目->属性->配置属性->链接器->调试->生成调试信息,这里设为『是』;

3、C/C++->优化->优化选择『禁用』;

4、删除解决方案下的.ncb文件;

5、工具->选项->调试->『要求源文件与原始版本完成匹配』去掉勾;

6、最后在上述设置的情况下,重新编译整个解决方案;

7、回过头来,发现原来是一段不会被执行到的代码……

看来,除了VS本身会有bug,自己的代码还是要多检查一下。(image)

Fox 2009-01-04 11:04 发表评论



简单的属性结构设计

Sat, 27 Dec 2008 18:44:00 GMT

本文最早发布于我的个人主页

一般的RPG游戏中,玩家角色、NPC、物品、场景等一般都具有为数众多的各种属性,使用C++编码时,很容易考虑设计成为大量的成员变量和相应的存取函数:

class CObject
{
public:
    long GetAttrA(void) const { return m_lAttrA; }
    void SetAttrA(long lVal) { m_lAttrA = lVal; }
    long GetAttrB(void) const { return m_lAttrB; }
    void SetAttrB(long lVal) { m_lAttrB = lVal; }
    ...
    long GetAttrN(void) const { return m_lAttrN; }
    void SetAttrN(long lVal) { m_lAttrN = lVal; }

private:
    long m_lAttrA;
    long m_lAttrB;
    ...
    long m_lAttrN;
};

乍一看,非常清晰明了。在一个稍显复杂的项目中,想记住、甚至找到每一个属性是非常难的,况且除了属性,还有大量的逻辑处理,甚至是不同项目间的数据交互,比如将属性的数据库存取。这么做的缺点大致有这么几点:

1. 属性没有明确分门别类,尤其在多人协作、多模块编写时,往往上面一批、下面一批,甚至有重复属性、废弃属性,难于管理和把握;

2. 对于数据库存取,需要写单独的存取接口,而且一旦属性有增减、修改,存取接口要随之修改;

3. 通过接口函数进行简单的属性存取面临大量的堆栈保存,即使使用内联或宏定义,也是治标不治本。

针对上述问题,我的思路也比较简单:对基本类型数据进行二次封装。

struct tagDBAttrs
{
    long lA;
    long lB;
    ...
    long lN;
};

class CObject
{
public:
    const tagDBAttrs& GetDBAttrs(void) const { return m_DBAttrs; }
    void SetDBAttrs(const tagDBAttrs& rDBAttrs)
    {
        memcpy(&m_DBAttrs, rDBAttrs, sizeof(m_DBAttrs));
    }

private:
    tagDBAttrs    m_DBAttrs;
    string        m_strA;
    CObject*      m_pObjB;
    CShape*       m_pShapeC;
    ...
};
 

之所以提到基本类型数据,主要缘于基本类型构成的结构可以通过sizeof运算符直接确定,而像类对象等组合类型,其深拷贝、赋值、操作及赋值等逻辑则较为复杂,一般无法统一处理。

以上面的tagDBAttrs为例,对于基本类型数据处理具有非常大的优势,尤其在数据整体存取时,此外,增减基本类型属性也比较简单,而且不需要重写Get/Set接口,同时方便了对属性的分门别类处理。


此处顺便谈谈关于开发中的一些杂的体会。

对于工作不久的同学而言,拿到一个任务有可能出现以下两种情况:

1. 以快速开发为重,前期进展较快,在小型模块开发中顺风顺水,但在面对复杂任务时,因为对前期设计重视不足,后期会不断调整代码,甚至在基本功能开发结束后,因为逻辑架构问题,导致bug隐患较多较深,容易陷入泥沼,痛苦挣扎;

2. 随时随处追求代码结构的优美和执行效率的优化,精益求精,往往在前期设计上花费过多的心思,以达到较合理的逻辑结构,甚至拖延整个项目的开发进度,这种同学一般说来,代码质量较高,后期bug较少。

其实,上面说的就是我自己,我刚开始工作的时候,上面给我一个编写独立工具的任务,当时急于表现,缺乏项目实战经验,为编码而编码,逻辑结构不注重扩展性,设计不合理,在进行到后期时,面对出现的新需求无法应对,导致最后推到重来。

后来,慢慢接受了注重前期设计的观念,更因为自己的完美情结和代码洁癖,不仅经常调整自己编码中不优美的片段,甚至越俎代庖去修改其他同事的代码,追求处处的高效,耗费大量时间精力,难以自拔。

在任何一个项目中,都有轻重主次之分,也就是所谓的『8020原则』,关于『8020原则』怎么理解都行,我的理解就是,每个项目中的效率瓶颈是20%的核心代码,80%的时间和工作重心应该放到这20%的核心代码上。放到一个小的模块中也是这样,应该确定工作重心,在开发进度的限制下,对核心代码精益求精,对非效率瓶颈可以适当减少侧重。

对于新人,最难的就是从整体的高度确定20%的工作重心,这主要依赖于自身素质的提高和开发经验的积累。而对需求深入细致的分析和对逻辑精益求精的设计本身可以训练一个人分析问题、解决问题的能力。

总结一下的话,也正是我们项目组历来所倡导和坚持的:

1. 精益求精;

2. 细节决定成败,态度制约能力。(image)

Fox 2008-12-28 02:44 发表评论



设计模式(三)——Singleton

Wed, 19 Nov 2008 15:37:00 GMT

不知道Singleton算不算用的最多的,平时用的时候,往往都是直接敲下面一段:

……

不是不想改,就是懒,敲多了已经不觉得这么写多浪费时间了,按大家的说法,这样写至少有这么几个缺点:

1. 必须在程序结束前手动释放,这不仅是RP问题,如果你借了内存不主动还,说明你RP差,但被别人搞丢了(宕机)导致你还不上,说明别人RP差?所以,这还是个问题;

2. 线程同步问题,如果Singleton实例跨线程使用,上例不安全,在Initial和Release时加锁可以解决;

3. 最大的问题:不能重用。

阅读全文

(image)

Fox 2008-11-19 23:37 发表评论



设计模式(二)——State

Tue, 18 Nov 2008 16:57:00 GMT

State模式对应到C++的多态特性。

State模式适用较广,这儿给出比较常见易懂的三种情况:

1. 当怪物在面对不同职业和特性的玩家时对应不同的AI处理和技能释放:

CSkill* pAttackSkill;

if( pPlayer->MagicImmune() ) pAttackSkill = SomePhysicalAttackSkill;

else if( pPlayer->PhysicalImmune() ) pAttackSkill = SomeMagicAttackSkill;

...

pAttackSkill->Begin();

...

或者使用分支结构:

CSkill* pAttackSkill;

switch( pPlayer->GetOccupation() )

{

  case WARRIOR: pAttackSkill = SomeLongRangeSkill; break;

  case MAGICIAN: pAttackSkill = SomeForceSkill; break;

  case NIMROD: pAttackSkill = SomeMagicSkill; break;

...

}

pAttackSkill->Begin();

...

阅读全文

(image)

Fox 2008-11-19 00:57 发表评论



WSAEventSelect剖析

Mon, 27 Oct 2008 15:25:00 GMT

本文同时发布在

近来在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杂的多,谈点技术的还是会同步更新到

(image)

Fox 2008-10-27 23:25 发表评论



支持成员函数指针的消息映射机制的简单实现

Fri, 10 Oct 2008 02:20:00 GMT

作者:Fox 本文同时发布在http://www.yulefox.com和http://www.cppblog.com/fox。 十天之前,在CPPBLOG上写了一篇消息映射机制的简单实现,有同学提到该实现不支持成员函数。这个问题我也考虑到了,既然被提出来,不妨把实现提供出来。 需要说明的是,我本身对template比较不感冒,不过Kevin Lynx对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 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 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     CFunction( FuncType &Func )         : m_pHandler(new CDefHandler(Func))     {     }     // 封装(非静态成员函数)     template     CFunction( ObjType* pObj, FuncType Func )         : m_pHandler(new CMemHandler(pObj, Func))     {     }     virtual ~CFunction()     {         DELETE_SAFE(m_pHandler);     }         // 函数调用     int operator()(CBaseMessage* pMsg)     {         return (*m_pHandler)(pMsg);     } private:     CHandler    *m_pHandler; }; typedef std::map MSG_MAP; typedef MSG_MAP::iterator MSG_ITR; class CMsgRegister { public:     // 注册消息函数(C函数或[...]



消息映射机制的简单实现

Mon, 29 Sep 2008 10:34:00 GMT

项目中使用了消息通信机制,因为消息类型非常多,相应的,处理消息的地方代码也非常多。 自然而然想到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 (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[] =  \  &nb[...]



再辨同步/异步与阻塞/非阻塞

Wed, 10 Sep 2008 17:11:00 GMT

网络编程学习和实践的过程中,同步(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:纠结一些必要的概念是为了加深理解,太过纠结了反倒会滞塞理解。我之前对于其概念也并非特别清楚,所以才会再续一篇特意言明,也算弥补一下自己的过失。

(image)

Fox 2008-09-11 01:11 发表评论



设计模式(一)

Wed, 06 Aug 2008 07:43:00 GMT

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种创建型DP:Abstract Factory、Builder、Factory Method、Builder、Prototype、Singleton。 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;&n[...]



Big-endian记忆(附闲扯)

Wed, 30 Jul 2008 06:48:00 GMT

一、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:你看老毛,与人斗其乐无穷,[...]



[译]Google C++编程风格指南(八)[完]

Wed, 23 Jul 2008 06:28:00 GMT

原文地址:Google C++ Style Guide 规则之例外 前面说明的编码习惯基本是强制性的,但所有优秀的规则都允许例外。 1. 现有不统一代码(Existing Non-conformant Code) 对于现有不符合既定编程风格的代码可以网开一面。 当你修改使用其他风格的代码时,为了与代码原有风格保持一致可以不使用本指南约定。如果不放心可以与代码原作者或现在的负责人员商讨,记住,一致性包括原有的一致性。 1. Windows代码(Windows Code) Windows程序员有自己的编码习惯,主要源于Windows的一些头文件和其他Microsoft代码。我们希望任何人都可以顺利读懂你的代码,所以针对所有平台的C++编码给出一个单独的指导方案。 如果你一直使用Windows编码风格的,这儿有必要重申一下某些你可能会忘记的指南(译者注,我怎么感觉像在被洗脑:D): 1) 不要使用匈牙利命名法(Hungarian notation,如定义整型变量为iNum),使用Google命名约定,包括对源文件使用.cc扩展名; 2) Windows定义了很多原有内建类型的同义词(译者注,这一点,我也很反感),如DWORD、HANDLE等等,在调用Windows API时这是完全可以接受甚至鼓励的,但还是尽量使用原来的C++类型,例如,使用const TCHAR *而不是LPCTSTR; 3) 使用Microsoft Visual C++进行编译时,将警告级别设置为3或更高,并将所有warnings当作errors处理; 4) 不要使用#pragma once;作为包含保护,使用C++标准包含保护,包含保护的文件路径包含到项目树顶层(译者注,#include); 5) 除非万不得已,否则不使用任何不标准的扩展,如#pragma和__declspec,允许使用__declspec(dllimport)和__declspec(dllexport),但必须通过DLLIMPORT和DLLEXPORT等宏,以便其他人在共享使用这些代码时容易放弃这些扩展。 在Windows上,只有很少一些偶尔可以不遵守的规则: 1) 通常我们禁止使用多重继承,但在使用COM和ATL/WTL类时可以使用多重继承,为了执行COM或ATL/WTL类及其接口时可以使用多重实现继承; 2) 虽然代码中不应使用异常,但在ATL和部分STL(包括Visual C++的STL)中异常被广泛使用,使用ATL时,应定义_ATL_NO_EXCEPTIONS以屏蔽异常,你要研究一下是否也屏蔽掉STL的异常,如果不屏蔽,开启编译器异常也可以,注意这只是为了编译STL,自己仍然不要写含异常处理的代码; 3) 通常每个项目的每个源文件中都包含一个名为StdAfx.h或precompile.h的头文件方便头文件预编译,为了使代码方便与其他项目共享,避免显式包含此文件(precompile.cc除外),使用编译器选项/FI以自动包含; 4) 通常名为resource.h、且只包含宏的资源头文件,不必拘泥于此风格指南。 团队合作 参考常识,保持一致。 编辑代码时,花点时间看看项目中的其他代码并确定其风格,如果其他代码if语句中使用空格,那么你也要使用。如果其中的注释用星号(*)围成一个盒子状,你也这样做:/********************************** * Some comments are here. * There may be many lines. **********************************/ 编程风格指南的使用要点在于提供一个公共的编码规范,所有人可以把精力集中在实现内容而不是表现形式上[...]



[译]Google C++编程风格指南(七)

Wed, 23 Jul 2008 03:43:00 GMT

原文地址:Google C++ Style Guide 格式 代码风格和格式确实比较随意,但一个项目中所有人遵循同一风格是非常容易的,作为个人未必同意下述格式规则的每一处,但整个项目服从统一的编程风格是很重要的,这样做才能让所有人在阅读和理解代码时更加容易。 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)总在最后一个参数同[...]



[译]Google C++编程风格指南(六)

Tue, 22 Jul 2008 09:02:00 GMT

原文地址:Google C++ Style Guide 注释 注释虽然写起来很痛苦,但对保证代码可读性至为重要,下面的规则描述了应该注释什么、注释在哪儿。当然也要记住,注释的确很重要,但最好的代码本身就是文档(self-documenting),类型和变量命名意义明确要比通过注释解释模糊的命名好得多。 注释是为别人(下一个需要理解你的代码的人)而写的,认真点吧,那下一个人可能就是你! 1. 注释风格(Comment Style) 使用//或/* */,统一就好。 //或/* */都可以,//只是用的更加广泛,在如何注释和注释风格上确保统一。 2. 文件注释(File Comments) 在每一个文件开头加入版权公告,然后是文件内容描述。 法律公告和作者信息: 每一文件包含以下项,依次是: 1) 版权(copyright statement):如Copyright 2008 Google Inc.; 2) 许可版本(license boilerplate):为项目选择合适的许可证版本,如Apache 2.0、BSD、LGPL、GPL; 3) 作者(author line):标识文件的原始作者。 如果你对其他人创建的文件做了重大修改,将你的信息添加到作者信息里,这样当其他人对该文件有疑问时可以知道该联系谁。 文件内容: 每一个文件版权许可及作者信息后,都要对文件内容进行注释说明。 通常,.h文件要对所声明的类的功能和用法作简单说明,.cc文件包含了更多的实现细节或算法讨论,如果你感觉这些实现细节或算法讨论对于阅读有帮助,可以把.cc中的注释放到.h中,并在.cc中指出文档在.h中。 不要单纯在.h和.cc间复制注释,复制的注释偏离了实际意义。 3. 类注释(Class Comments) 每个类的定义要附着描述类的功能和用法的注释。// Iterates over the contents of a GargantuanTable. Sample usage: // GargantuanTable_Iterator* iter = table->NewIterator(); // for (iter->Seek("foo"); !iter->done(); iter->Next()) { // process(iter->key(), iter->value()); // } // delete iter; class GargantuanTable_Iterator { ... }; 如果你觉得已经在文件顶部详细描述了该类,想直接简单的来上一句“完整描述见文件顶部”的话,还是多少在类中加点注释吧。 如果类有任何同步前提(synchronization assumptions),文档说明之。如果该类的实例可被多线程访问,使用时务必注意文档说明。 4. 函数注释(Function Comments) 函数声明处注释描述函数功能,定义处描述函数实现。 函数声明: 注释于声明之前,描述函数功能及用法,注释使用描述式("Opens the file")而非指令式("Open the file");注释只是为了描述函数而不是告诉函数做什么。通常,注释不会描述函数如何实现,那是定义部分的事情。 函数声明处注释的内容: 1) inputs(输入)及outputs(输出); 2) 对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数; 3) 如果函数分配了空间,需要由调用者释放; 4) 参数是否可以为NULL; 5) 是否存在函数使用的性能隐忧(performance implications); 6) 如果函数是可重入的(re-entrant),其同步前提(synchronization assumptions)是什么? 举例如下:// Returns an it[...]



[译]Google C++编程风格指南(五)

Tue, 22 Jul 2008 03:59:00 GMT

原文地址:Google C++ Style Guide 命名约定 最重要的一致性规则是命名管理,命名风格直接可以直接确定命名实体是:类型、变量、函数、常量、宏等等,无需查找实体声明,我们大脑中的模式匹配引擎依赖于这些命名规则。 命名规则具有一定随意性,但相比按个人喜好命名,一致性更重要,所以不管你怎么想,规则总归是规则。 1. 通用命名规则(General Naming Rules) 函数命名、变量命名、文件命名应具有描述性,不要过度缩写,类型和变量应该是名词,函数名可以用“命令性”动词。 如何命名: 尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要,好的命名选择:int num_errors; // Good. int num_completed_connections; // Good. 丑陋的命名使用模糊的缩写或随意的字符:int n; // Bad - meaningless. int nerr; // Bad - ambiguous abbreviation. int n_comp_conns; // Bad - ambiguous abbreviation. 类型和变量名一般为名词:如FileOpener、num_errors。 函数名通常是指令性的,如OpenFile()、set_num_errors(),访问函数需要描述的更细致,要与其访问的变量相吻合。 缩写: 除非放到项目外也非常明了,否则不要使用缩写,例如:// Good // These show proper names with no abbreviations. int num_dns_connections; // Most people know what "DNS" stands for. int price_count_reader; // OK, price count. Makes sense. // Bad! // Abbreviations can be confusing or ambiguous outside a small group. int wgc_connections; // Only your group knows what this stands for. int pc_reader; // Lots of things can be abbreviated "pc". 不要用省略字母的缩写:int error_count; // Good.int error_cnt; // Bad. 2. 文件命名(File Names) 文件名要全部小写,可以包含下划线(_)或短线(-),按项目约定来。 可接受的文件命名: my_useful_class.ccmy-useful-class.ccmyusefulclass.cc C++文件以.cc结尾,头文件以.h结尾。 不要使用已经存在于/usr/include下的文件名(译者注,对UNIX、Linux等系统而言),如db.h。 通常,尽量让文件名更加明确,http_server_logs.h就比logs.h要好,定义类时文件名一般成对出现,如foo_bar.h和foo_bar.cc,对应类FooBar。 内联函数必须放在.h文件中,如果内联函数比较短,就直接放在.h中。如果代码比较长,可以放到以-inl.h结尾的文件中。对于包含大量内联代码的类,可以有三个文件:url_table.h // The class declaration. url_table.cc // The class definition. url_table-inl.h // Inline functions that include lots of code. 参考第一篇-inl.h文件一节。 3. 类型命名(Type Names) 类型命名每个单词以大写字母开头,不包含下划线:MyExcitingClass、MyExcitingEnum。 所有类型命名——类、结构体、类型定义(typedef)、枚举——使用相同约定,例如: // classes and structs class UrlTable { ... class UrlTableTester { ... struct UrlTableProperties { ... // typedefs typedef hash_map PropertiesMap; // enums enum UrlTableErrors { ... 4. 变量命名(Variable Names) 变量[...]



[译]Google C++编程风格指南(四)

Mon, 21 Jul 2008 06:55:00 GMT

原文地址:Google C++ Style Guide Google特有的风情 Google有很多自己实现的使C++代码更加健壮的技巧、功能,以及有异于别处的C++的使用方式。 1. 智能指针(Smart Pointers) 如果确实需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器中对象,你应该只使用std::tr1::shared_ptr,任何情况下都不要使用auto_ptr。 “智能”指针看上去是指针,其实是附加了语义的对象。以scoped_ptr为例,scoped_ptr被销毁时,删除了它所指向的对象。shared_ptr也是如此,而且,shared_ptr实现了引用计数(reference-counting),从而只有当它所指向的最后一个对象被销毁时,指针才会被删除。 一般来说,我们倾向于设计对象隶属明确的代码,最明确的对象隶属是根本不使用指针,直接将对象作为一个域(field)或局部变量使用。另一种极端是引用计数指针不属于任何对象,这样设计的问题是容易导致循环引用或其他导致对象无法删除的诡异条件,而且在每一次拷贝或赋值时连原子操作都会很慢。 虽然不推荐这么做,但有些时候,引用计数指针是最简单有效的解决方案。 译者注:看来,Google所谓的不同之处,在于尽量避免使用智能指针:D,使用时也尽量局部化,并且,安全第一。 其他C++特性 1. 引用参数(Reference Arguments) 所以按引用传递的参数必须加上const。 定义:在C语言中,如果函数需要修改变量的值,形参(parameter)必须为指针,如int foo(int *pval)。在C++中,函数还可以声明引用形参:int foo(int &val)。 优点:定义形参为引用避免了像(*pval)++这样丑陋的代码,像拷贝构造函数这样的应用也是必需的,而且不像指针那样不接受空指针NULL。 缺点:容易引起误解,因为引用在语法上是值却拥有指针的语义。 结论: 函数形参表中,所有引用必须是const: void Foo(const string &in, string *out); 事实上这是一个硬性约定:输入参数为值或常数引用,输出参数为指针;输入参数可以是常数指针,但不能使用非常数引用形参。 在强调参数不是拷贝而来,在对象生命期内必须一直存在时可以使用常数指针,最好将这些在注释中详细说明。bind2nd和mem_fun等STL适配器不接受引用形参,这种情况下也必须以指针形参声明函数。 2. 函数重载(Function Overloading) 仅在输入参数类型不同、功能相同时使用重载函数(含构造函数),不要使用函数重载模仿缺省函数参数。 定义:可以定义一个函数参数类型为const string&,并定义其重载函数类型为const char*。 class MyClass {public:  void Analyze(const string &text);  void Analyze(const char *text, size_t textlen);}; 优点:通过重载不同参数的同名函数,令代码更加直观,模板化代码需要重载,同时为访问者带来便利。 缺点:限制使用重载的一个原因是在特定调用处很难确定到底调用的是哪个函数,另一个原因是当派生类只重载函数的部分变量会令很多人对继承语义产生困惑。此外在阅读库的客户端代码时,因缺省函数参数造成[...]



日志该怎么记录?

Fri, 18 Jul 2008 02:03:00 GMT

一个好的日志系统,除了可以记录尽可能多的必要信息,方便trace bugs、提供data analysis source这些基本功能之外,其他的貌似不必太在意。但真正当bugs冒出来的时候,要命的是既没有dump,也没有有价值的日志,更要命的是日志居然已经记录了那么多,居然让你查了半天,居然都是没有价值的!

悲剧啊!

日志需要记录的信息大概分为两类:

1) 系统运行情况:启动、加载、读写、关闭、异常

2) 用户使用情况:进入、操作、离开、异常

我可以想到的关于日志系统的要求大致以下几点:

1) 日志系统使用目录树结构:系统日志和用户日志分别记录,正常日志和异常日志分别记录,不置于同一文件夹下,日志文件命名做到令观者一目了然;

2) 记录详尽但不冗余:正确记录日志时间、位置、事件、因果,有可能的话,记录上下文(这要求有点高了);

3) 格式统一但严禁千篇一律:格式统一是指记录内容遵循一定格式,方便查看,严禁千篇一律是指记录要有层次、轻重,不同事件导致的“同一”异常日志不应不加区别,同样是为了方便查看;

4) 与异常处理相辅相成:有dump时,以日志辅助快速定位,没有dump时,日志应尽可能提供有效信息,离系统崩溃的地方越近越好(这一点似乎也有难度)。

________________________________________________

突然想到的,也还没有动手去做,先记下了,欢迎补充。

_____Added on Jul.25th, 2008_______________________

还看到一位兄弟在为我说话,谢谢!

今天在考虑实现时,想到一个很现实的问题,日志几乎是无处不在的,随时随地会有日志记录。不知道有谁对I/O(当然主要是Output)消耗和对系统的影响做过专门测试,猜测就算了:-),我很想知道有没有必要放到专门的线程中,如果放到独立线程中的话,问题就出来了,多长时间写一次?毕竟,记录日志的主要目的就是为了全面记录系统运行和用户使用情况,如果在服务器crash的时候,还有日志(尤其是crash上下文日志)没有被顺利写入,日志的意义也就大打折扣。

谁给点建议?

(image)

Fox 2008-07-18 10:03 发表评论



[译]Google C++编程风格指南(三)

Wed, 16 Jul 2008 09:43:00 GMT

这一篇主要提到的是类,Lippman在《Inside The C++ Object Model》第二章中对构造函数作了详尽说明,本文中提到的几个单词基本仿该书中译本侯捷先生的翻译: explicit:明确的 implicit:隐含的 trivial:没有意义的 non-trivial:有意义的   原文地址:Google C++ Style Guide 类 类是C++中基本的代码单元,自然被广泛使用。本节列举了在写一个类时要做什么、不要做什么。 1. 构造函数(Constructor)的职责 构造函数中只进行那些没有实际意义的(trivial,译者注:简单初始化对于程序执行没有实际的逻辑意义,因为成员变量的“有意义”的值大多不在构造函数中确定)初始化,可能的话,使用Init()方法集中初始化为有意义的(non-trivial)数据。 定义:在构造函数中执行初始化操作。 优点:排版方便,无需担心类是否初始化。 缺点:在构造函数中执行操作引起的问题有: 1) 构造函数中不易报告错误,不能使用异常。 2) 操作失败会造成对象初始化失败,引起不确定状态。 3) 构造函数内调用虚函数,调用不会派发到子类实现中,即使当前没有子类化实现,将来仍是隐患。 4) 如果有人创建该类型的全局变量(虽然违背了上节提到的规则),构造函数将在main()之前被调用,有可能破坏构造函数中暗含的假设条件。例如,gflags尚未初始化。 结论:如果对象需要有意义的(non-trivial)初始化,考虑使用另外的Init()方法并(或)增加一个成员标记用于指示对象是否已经初始化成功。 2. 默认构造函数(Default Constructors) 如果一个类定义了若干成员变量又没有其他构造函数,需要定义一个默认构造函数,否则编译器将自动生产默认构造函数。 定义:新建一个没有参数的对象时,默认构造函数被调用,当调用new[](为数组)时,默认构造函数总是被调用。 优点:默认将结构体初始化为“不可能的”值,使调试更加容易。 缺点:对代码编写者来说,这是多余的工作。 结论: 如果类中定义了成员变量,没有提供其他构造函数,你需要定义一个默认构造函数(没有参数)。默认构造函数更适合于初始化对象,使对象内部状态(internal state)一致、有效。 提供默认构造函数的原因是:如果你没有提供其他构造函数,又没有定义默认构造函数,编译器将为你自动生成一个,编译器生成的构造函数并不会对对象进行初始化。 如果你定义的类继承现有类,而你又没有增加新的成员变量,则不需要为新类定义默认构造函数。 3. 明确的构造函数(Explicit Constructors) 对单参数构造函数使用C++关键字explicit。 定义:通常,只有一个参数的构造函数可被用于转换(conversion,译者注:主要指隐式转换,下文可见),例如,定义了Foo::Foo(string name),当向需要传入一个Foo对象的函数传入一个字符串时,构造函数Foo::Foo(string name)被调用并将该字符串转换为一个Foo临时对象传给调用函数。看上去很方便,但如果你并不希望如此通过转换生成一[...]



[译]Google C++编程风格指南(二)

Mon, 14 Jul 2008 07:49:00 GMT

原文地址:Google C++ Style Guide 作用域 1. 命名空间(Namespaces) 在.cc文件中,提倡使用不具名的命名空间(unnamed namespaces,译者注:不具名的命名空间就像不具名的类一样,似乎被介绍的很少:-()。使用具名命名空间时,其名称可基于项目或路径名称,不要使用using指示符。 定义:命名空间将全局作用域细分为不同的、具名的作用域,可有效防止全局作用域的命名冲突。 优点:命名空间提供了(可嵌套)命名轴线(name axis,译者注:将命名分割在不同命名空间内),当然,类也提供了(可嵌套)的命名轴线(译者注:将命名分割在不同类的作用域内)。 举例来说,两个不同项目的全局作用域都有一个类Foo,这样在编译或运行时造成冲突。如果每个项目将代码置于不同命名空间中,project1::Foo和project2::Foo作为不同符号自然不会冲突。 缺点:命名空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轴线。在头文件中使用不具名的空间容易违背C++的唯一定义原则(One Definition Rule (ODR))。 结论:根据下文将要提到的策略合理使用命名空间。 1) 不具名命名空间(Unnamed Namespaces) 在.cc文件中,允许甚至提倡使用不具名命名空间,以避免运行时的命名冲突: namespace {                                   // .cc 文件中 // 命名空间的内容无需缩进enum { UNUSED, EOF, ERROR };          // 经常使用的符号bool AtEof() { return pos_ == EOF; }   // 使用本命名空间内的符号EOF }  // namespace 然而,与特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员或静态成员函数,而不是不具名命名空间的成员。像上文展示的那样,不具名命名空间结束时用注释// namespace标识。 不能在.h文件中使用不具名命名空间。 2) 具名命名空间(Named Namespaces) 具名命名空间使用方式如下: 命名空间将除文件包含、全局标识的声明/定义以及类的前置声明外的整个源文件封装起来,以同其他命名空间相区分。 // .h文件namespace mynamespace { // 所有声明都置于命名空间中// 注意不要使用缩进class MyClass {public:  ...  void Foo();}; }  // namespace mynamespace // .cc文件namespace mynamespace { // 函数定义都置于命名空间中void MyClass::Foo() {  ...} }  // namespace mynamespace 通常的.cc文件会包含更多、更复杂的细节,包括对其他命名空间中类的引用等。 #include "a.h" DEFINE_bool(someflag, false, "dummy flag"); class C;  // 全局命名空间中类C的前置声明namespace a { class A; }  // 命名空间a中的类a::A的前置声明 namespace b { ...code for b...                // b中的代码 }  // namespace b 不要声明命名空间std下的任何[...]