这本书或许能从一个侧面反映国内C++开发的大体水平

陈硕 评论 0 bug 4 2010-01-07 03:20:37
郑晖
郑晖 2010-01-07 14:52:38

>>有几点我很喜欢,第一是没有用异常,第二是没有用继承(也就没有虚函数、设计模式这些东西),第三是只出现过一个类模板
    
  >>也有几点我不喜欢,整本书的代码基本上都是披着 C++ 外衣(马甲)的 C 代码
  
  这两段话似乎有些矛盾。既然您喜欢没有异常、没有继承(也就没有多态)、没有STL的c++代码,再加上大量的goto和宏,那么这种代码基本上就是披着c++外衣的C 代码(除了比C多一点类的封装),那么后来为何又说不喜欢呢?或许只能这么解释,您认为c++相对于C的价值主要体现在有封装机制的类和标准库?

ylvalili
ylvalili 2010-01-07 14:53:13

牛B啊!

1036
1036 2010-01-07 15:14:06

看了这篇文章,知道了自己到底有多菜!

陈硕
陈硕 2010-01-07 16:00:37

> 这两段话似乎有些矛盾。既然您喜欢没有异常、没有继承(也就没有多态)、没有STL的c++代码,再加上大量的goto和宏,

您误读了我的原意,我喜欢没有异常、没有继承(也就没有多态)的代码,不喜欢 goto 和宏。“不喜欢”的大致含义是“能不用就不用,除非其他办法更丑陋”,异常和继承也只有迫不得已才在局部使用。我经常用STL的某些组件。

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-07 21:04:10

嗯,我也说几句。
几位大牛确实一针见血,说出了我很多缺点和弱点,在此表示感谢。
嗯,我还是申辩几句哈。
没有完全使用C++,固然有我本人水平不够的影子,呵呵,我自我评价,我是标准的山寨C++程序员,嘿嘿。
但,中间大牛们百思不得其解的一个奇怪写法,我这里解释一下。
为什么不去直接用C++的类封装,而是费尽心机写了一个结构体封装类成员数据,然后写一堆C函数,然后再用类来封装。这是有原因,原因呢,书里面也写了。
关键是为了预防内存碎片。
C和C++的动态内存分配机制,有个问题,长期,大量申请和释放后,会有内存碎片,严重的会导致服务器挂死,而我开发的很多工程,都有7*24小时工作需求,因此,内存碎片普通程序员可能都感觉不到,却成了我必须解决的难题。
因此我做了个内存池,主要是基于重用理念,仿造STL的内存池构建方法,建立自己的多线程安全内存池。
但是,这个内存池有个缺点,只能提供malloc和free服务,不具备new和delete操作,我编译原理学得不好,确实不知道怎么做,呵呵。
这就说明,我的内存池,只能为纯数据区块服务,无法应对动态对象的申请和释放。
但是,C++的对象在大规模工程组织中的便利性我又舍不得放弃,没办法,我就做了个奇奇怪怪的方法。
对于某些高频申请和释放,且长度不等的应用需求,比如,传输服务器常见的动态哈希表,用于目标地址,或者一些报文的快速存放和取出的工具,我习惯于这么做。
1、类里面手工区别成员变量和成员函数,先用C的struct实现成员变量组,然后用纯C方式提供访问逻辑。
2、用类封装,封装时,内部所有成员变量利用一个struct指针来表示,这时候,这个struct可以在内存池上分配。
我们知道,通常一个对象的绝对尺寸,主要还是取决与内部的数据,函数表占不了多少字节的,几十个字节顶天了,当占主要尺寸的内部数据区使用内存池管理,规避了内存碎片之后,几十个字节的对象,一般说来,随便new和delete,都不会再有内存碎片的风险了。
主要目的就是这个,不知道我讲清楚了没有。
这是实战出来的经验,因为过去很多次,我的服务器就是因为这个原因挂掉的,而且,很不好找原因。
嗯,再说一点,原子锁我研究过,不过,我发现,原子锁和操作系统关系太密切,我要求我的库是跨平台的,嗯,这个就不劳大家批评了,什么效率什么的,这是我的设计需求。
因此,我一般不太喜欢粘操作系统太多的东西,所以,我在锁上面又做了一层封装,实现了跨平台的安全调用机制,至少从我的工作中,这种努力时值得的,因为我通常同时写一个传输协议的客户端和服务器端,而客户端使用Windows平台,服务器端是Linux平台,二者还都有可能是arm linux平台。
这么写,看似效率不高,很怪异,但总好过我同时维护几套库。大家说是不是?
嗯,析构函数内无意义的加锁和解锁,我是作为一个小经验分享的,叫做有它更好,没有也无所谓。
近年来,我使用标准成员函数定名的方式,以Start和Stop作为一个包含线程的类的显示起停标志,并且使用防止重入锁规避重入错误,已经是很成熟的编程手法。
目前我开发时,析构函数习惯性调用Stop,确保本类所属所有线程均已经安全退出再释放,其实已经足以保证安全性了,嗯,书上的这个无意义加锁和解锁,其实在很多时候没必要。
我提出这个经验值,主要是考虑到,有些纯工具函数类,可能被n个线程调用,如果程序员不小心,可能会在析构对象时,会导致还有线程没有退出,在访问这个对象,导致挂死。
由于这种情况往往多发生在程序退出时,其实就是有个时间差,也许由于程序员不小心,会有一些线程多活几个周期,多碰几次这个对象,这个,我能避免,但不是所有的程序员都能避免。
因此,我介绍这个经验,就是尽最大可能,帮助程序员在没有控制好的时候,能规避一点,就规避一点。
我也同意,这不是解决问题正确之道,但,有时候能救命,呵呵。

最后我还是要说,我仅仅是一个普通程序员,书中是我自己的一点实战经验,理论可能不一定全对,不过,几乎每个知识点,都是经过bug总结出来的,背后,往往都是十天半个月的加班,痛定思痛,总结一点,呵呵,说经验,都还不如说教训比较准确。
我从来没有认为自己有能力代表国内程序员的水平,我甚至连我们公司都代表不了,我仅仅代表我自己。

最后,还是谢谢各位大牛的关注,如果有问题,欢迎继续讨论。

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-07 21:10:20

补充一点,goto的使用见仁见智,我坚持我的看法。
在目前并行开发的环境下,很多时候,我们会碰到二元动作。
malloc-free
new-delete
lock-unlock
。。。
尤其是由于锁的加入,二元动作嵌套时,是不允许跨大括号跳出的,必须层级跳出,即嵌套多少层,就要跳跃多少次。这些都带来了编程的麻烦。
因此,我有个总结,多任务开发环境下,对于跳转的精准控制要求到了一个变态的地步。
传统结构化程序设计,由于起源比较早,提出的很多理念,是基于单任务开发环境下总结的,对于多任务环境,不尽适用。这个goto我认为就是一例。
结构化程序设计,强调实用break和continue来代替goto,但由于这二者都没有能力精确标定落点,开发中起到的效果,很多时候还不如goto。
所以,我坚持,goto不是不能用,但是不要乱用。
我书中讲过上述道理的。
嗯,宏是个人习惯,我书中强调,我既不说宏好,也不说不好,请各位读者自己选用。
不知道我解释清楚没有?

陈硕
陈硕 2010-01-07 22:13:28

感谢本书作者的回复。

[已注销]
[已注销] 2010-01-09 21:40:58

2010-01-07 21:43:39 肖舸
-------------------------------------
我支持你,赠书拿来。
:-)

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-09 21:51:30

决不!
我就剩1本了,是debug和给我儿子留着的。
其他全部被抢光了。
IT的小弟,有时候也像狼一样,嘿嘿。

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-11 13:27:33

用大括号就好了,函数内部可以多个自定义大括号的,大括号内部可以任意定义新的变量。

RednaxelaFX
RednaxelaFX (Script Ahead, Code Behind) 2010-01-11 20:30:10

看到楼上night_stalker的乱入,还没读这本书的菜鸟(俺)以顶楼提供的信息为基础也来乱入一下。

C++自行实现对象池之后,要new的话可以用placement-new,不知道肖舸大大有没有考虑一下?然后placement-new跟sizeof()+自定义的malloc()之类的可以组合起来写个宏,用的时候似乎也不会多写多少代码。也有人喜欢重载new……我没啥看法,看法留给大牛们发表。
如果想支持可移动对象的对象池,可以在引用对象时多加个间接层,以类似shared_ptr的方式去引用对象;在老的Sun的JVM里这种增加了间接层的方式叫handle-based object。不过……间接层多了肯定有人不爽,性能上也要付出相应的代价。要不要这么做自然又是见仁见智。

提到原子操作和操作系统的依赖问题,想起微软的SSCLI、.NET Micro Framework等底下都有所谓的PAL,platform abstraction layer。Windows中的HAL用于抽象硬件细节,而这里提到的PAL用于抽象“平台”(包括硬件与操作系统)细节。既然是微软做的东西,这里提到的PAL的API设计就跟微缩版Win32 API一样也不足为奇;其中Interlocked系API也有所实现。例如说:
unix-i386:
LONG
PALAPI
InterlockedIncrement(
IN OUT LONG volatile *lpAddend)
{
LONG RetValue;

__asm__ __volatile__(
"lock; xaddl %0,(%1)"
: "=r" (RetValue)
: "r" (lpAddend), "0" (1)
: "memory"
);

return RetValue + 1;
}

unix-ppc:
ASMFUNC InterlockedIncrement
sync
lwarx a1, 0, a0
addi a1, a1, 1
stwcx. a1, 0, a0
bne- _InterlockedIncrement
sync
mr a0,a1
blr

给自己的系统增加一个PAL倒是一劳永逸的把自己需要的功能从平台中抽象了出来,不过要花的功夫也会很多,值不值得就又是另外一回事……说来.NET Micro Framework 4.0是Apache 2.0许可证开源的,拿里面的PAL来用应该也没问题。

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-11 21:17:32

惭愧,我没你研究得细,学习了。
嗯,我和陈硕老师换了个地方在讨论,在这里,不妨去看一看,他提到了placement-new的。
http://student.csdn.net/space.php?uid=39028&do=blog&id=21198

RednaxelaFX
RednaxelaFX (Script Ahead, Code Behind) 2010-01-11 22:28:08

> http://student.csdn.net/space.php?uid=39028&do=blog&id=21198
这个链接打不开 =_=

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-11 23:00:34

CSDN学生大本营自从搬到教育网后,访问效果很烂。
我昨天还发飙呢。
他们答应周四换回来。之后就没事了。
你==看吧。
或者,去这里:
http://hi.csdn.net/link.php?url=http://blog.csdn.net%2Ftonyxiaohome

RednaxelaFX
RednaxelaFX (Script Ahead, Code Behind) 2010-01-11 23:31:52

嗯,不错,确实都是非常有经验的程序员从实践而来的感想。我是菜鸟而且没CSDN帐号,就不在您的blog里回复了。

C++代码中“玩继承”而不用虚函数的状况也不少见。例如好些虚拟机的对象系统的实现就是这样的,像是CLR、HotSpot之类。它们会用C++的类写对象系统中的基类Object在native一侧的实现,也会为一些特殊对象,如数组之类在C++里写继承自Object的类。它们不在这种地方用虚函数的原因很简单:它们要能精确控制对象布局,这样才方便实现GC和运行时的其它部分;常见C++编译器为虚函数生成的vtable对此是个干扰。

异常这玩儿也像围城一样。C++里异常加入固然是太晚了点,导致它的设计与语言没有很好的融合在一起,但最大的问题并不是因为C++的异常是unchecked的吧?Java这边在用了checked exception之后,许多人又在叫说这样写代码反而容易出错,因为它会引诱图省事的程序员写空的catch块把异常“吞掉”,那样反而更糟,系统会在不可靠的状态下继续运行下去。后面出现的类似Java的语言,C#,就又采用了unchecked exception的方案,还是靠文档来说明会抛的异常类型。到底怎么做好,可能还真的是得放到具体团队,在具体场景下才好说了。

菜鸟观点,仅供一笑 ^_^

foreverflying
foreverflying (账号自毁模式) 2010-01-12 05:06:24

别太执着于语言细节……
试着把c++当java用,把java当c#用,把c#当ruby用;——总之,用所有的oo语言中基本通用的特性,来解决90%,甚至是100%的问题。时间长了能发现,绝大部分需要解决的难题其实都是逻辑问题,是设计问题,而不是什么语言问题。
当然,c++还可以当c用,但c何尝不能当c++用呢。其实oo的精髓所在:面向接口编程,用接口的聚合而不用继承。能窥其本质的人,不会说c语言不能oo这种话。

foreverflying
foreverflying (账号自毁模式) 2010-01-12 05:39:59

c++语法的繁杂,让很多学的人,其中包括能学明白这些语法的人,迷失在语法细节的海洋里,而忘了编程语言的初衷——实现设计,解决问题。
曾看到不少人在争论函数该不该用const修饰,++i还是i++,private还是protect,利用构造析构特性来实现资源锁,还是老老实实地申请释放……等等等等这些无关紧要的细节,举不胜举。
这么说吧,依靠语言来特性解决问题的想法,始终是语言爱好者的一厢情愿;而在这上费功夫带来的收益,更是微乎其微。打个比方,使用智能指针你也无法保证资源一定在正确的时间被正确地释放;它的“智能”是以你的“智能”完全理解它的工作方式为前提的;而且最终,与你把分配释放两条语句放在正确的地方是等效的,仅仅是写法的不同而已。
说个极端点的例子:一个设计优良的程序,并不在乎把它的所有类成员都声明为public!真正明白该怎么做的程序员,不会去破坏应有的封装,访问不该看到的数据,即使它声明为public;不会去动const变量,即使它没有用const声明!不会做出多重代码继承的设计,即使c++完全支持这么做!倒过来说,c++魔幻的语法,让真想倒行逆施的人完全有这种办法,绕开const, private等等等等这些限制。它的强大,能保证你想写得多漂亮都可以,你想写得多丑陋也可以。
所以,别在这上费太多时间,多去关注些语言无关的东西吧,结构和设计,才是千奇百怪的语法存在的目的。

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-12 09:07:08

呵呵,写得很好,拜读了,也学习了。
你的说法我很同意,没有绝对的好,也没有绝对的不好,合用就是最好。
幸会。

foreverflying
foreverflying (账号自毁模式) 2010-01-12 14:28:05

呵呵,楼主身为c++资深人士,真是谦逊有加,赞。
看你的名字,似乎还有些印象,好像是我03、04年还在csdn混的时候,曾在我的帖子里见到过你,记得是当时级别分在四星以上的大牛之一。
不过也可能我记忆有偏差……产生虚假记忆这种事情可能随时发生,不须怪我,哈哈。
虽然我现在不会去读这一类初级中级的技术书籍了,不过对楼主花了大量精力把自己的所得及教训整理给后来人用的做法,非常赞赏。
很多时候,看着初学者被市面上各种“手把手教学方式”的技术书籍牵着,把大量的精力浪费在具体技术的细枝末节上;在业界浸淫多年,看见树木的数量仅能与从业时间等比增长,大局观方面的成长几乎为零,如何能让他看到森林呢。
所以常有这种冲动:把自己这些年的经验教训以及对关注方向的理解写成文字,让他们少走点弯路。然而,仅仅是想法而已,始终没有一个动力推着我把这想法变成实践。没有实践的想法,再好也只能等于零;这是我必须为楼主竖个大拇指的原因——现实中所有踏踏实实的实践者,都值得我们佩服。
楼主加油。

foreverflying
foreverflying (账号自毁模式) 2010-01-12 14:49:41

上面写错,不是楼主,是本书作者,呵呵,实在抱歉……

在这之前的那两段,是针对楼主的言论的意见,而非针对肖老师。
在很多c++具体细节的问题上,我与肖老师观点是一致的。
比如,我说“用所有oo语言基本通用的特性”,这其中就包含:不要用c++的拷贝构造函数——在99.99%的情况下,只传递指针,就如同java、ruby、python、js、as这类跑在虚拟机上的语言,仅仅传递引用一样,你见过它们在赋值的时候还要把对象克隆一份吗?

RednaxelaFX
RednaxelaFX (Script Ahead, Code Behind) 2010-01-12 15:26:03

.NET里的值类型就是完整复制的……通过C#的struct、VB.NET的Structure等形式暴露出来,就是“赋值的时候把对象克隆一份”。

肖舸
肖舸 (http://tonyxiaohome.blog.51cto) 2010-01-12 17:17:01

嗯,02、03年我是已经开始混CSDN,那会用户名叫做xghome,结果后来密码搞忘了,连注册邮箱都废了,搞得我去年只有用tonyxiaohome重出江湖,呵呵。
嗯,谢谢大家的鼓励,我会继续努力的。
这里多说一点哈:
近期我在设计一些模块api的时候,发现传址调用要好一点,因为很多时候,如果数据中包含操作系统锁变量等特殊变量时,传值显然是错的,比如把一个结构体拷贝到队列中,再拉出来,锁就完全失去意义了。而队列仅仅保留指针,其实原始数据区不作废,则继续有效。
这个问题我近期才发现重要性,可能以后要研究一下了,这里给大家提个醒,在锁相关时,要避免拷贝动作,尽量传址。
因此,C++很多时候的拷贝构造函数,我看着冒冷汗。
请问大家有对此熟悉的吗?我目前只是观察到现象,还没有完全理解。

西山
西山 (千里之行,始于足下) 2010-01-17 22:39:30

看完了,的确是一场高质量的讨论。也提些看法:

>>我认为 class 的拷贝构造和赋值应该默认是 private 的才合理
虽然有些类我们需要禁掉拷贝构造和赋值,但我所遇到的大多数类还是需要这些默认的拷贝构造和赋值的,这点在使用std容器的容易尤为明显。所以,这点我认为C++的设计没有问题。

>>观点1: 这恐怕很难算是合格的 C++ 程序员(我相信作者是个合格的 C 程序员)
>>观点2:这本书或许能从一个侧面反映国内 C++ 开发的大体水平
楼主这两个观点融合一下,就是国内C++开发者其实都不是合格的C++程序员,这点,我觉得低估了国内C++程序员的水平。

>>肖舸:通常一个对象的绝对尺寸,主要还是取决与内部的数据,函数表占不了多少字节的,几十个字节顶天了,当占主要尺寸的内部数据区使用内存池管理,规避了内存碎片之后,几十个字节的对象。
函数表是不占对象的尺寸的吧,即使是虚函数表,也只是放一个虚表指针在对象内。BTW,很喜欢作者这种共同探讨的谦虚与诚实。

>>foreverflying:曾看到不少人在争论函数该不该用const修饰,++i还是i++,private还是protect,利用构造析构特性来实现资源锁,还是老老实实地申请释放……等等等等这些无关紧要的细节,举不胜举。
每种语言都有其好的用法,这是其作为一门语言的特点,我觉得这关乎如何更好的使用一门语言的大局,RAII, const, reference等等其实这并非小节。

jinhu
jinhu 2010-01-28 22:29:38

哈哈,这几天这里的讨论这么激烈啊!
我客观分析一下。
肖老师是个经验丰富的开发者,具备写好程序的素质,不说别的,但看写的书的目录就知道是一个绝对的写程序的老鸟。
Shuo少侠的技术功底是相当雄厚的,Shuo具备熟练的c++技能,雄厚的算法功底,对boost的狂热追捧,以及追求代码完美度的偏执。
肖老师虽然经验丰富,但是给人感觉是博而不精。如果出书前能找shuo少侠这样一位严谨型的朋友进行校刊的话,相信这本书是一本绝对的好书。

Kouya
Kouya 2010-02-04 03:00:46

twitter观光团路过~本人也从学习到迷恋了Boost~对非oo的东西很反感。

其实C语言也是有oo实现的,比如DirectFB其实就是C写的,Sqlite3也是,但是它们和我的基于C++的Boost库毫不冲突,很容易就能融合进入我的C++代码,因为它们的思想是oo的!

tony.xiao.
tony.xiao. 2012-03-28 18:14:43

《0bug-C/C++工程之道》这本书作者已经公开,欢迎下载。地址在这里:http://tonyxiaohome.blog.51cto.com/925273/813229

exlife
exlife (遇到弱智泼妇,也是醉了) 2012-06-06 23:24:47

你说为了避免内存碎片,自己写一个内存池。以及7*24长期运行的程序和内存碎片的关系,这些观点我不太相信你的说法是对的。
首先你是否了解系统对内存的管理?我很担心你实际上是做了一成无用功,注意你自己做的这套东西对错暂且不论,对运行效率这不是没有代价的。

mr.nop
mr.nop (银鳞胸甲,蓝色品质,5g一件) 2012-07-25 00:00:05

   foreverflying
  绝大部分需要解决的难题其实都是逻辑问题,是设计问题,而不是什么语言问题。
  
  +1