2

什么时候用C而不用C++?

 3 years ago
source link: https://www.skywind.me/blog/archives/1407
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

什么时候用C而不用C++?

知乎问题《什么时候用C而不用C++?》:

前两天不是有一个问题是“什么时候用C++而不用C”,我一直觉得问错了,难道不是“能用C++就不用C”么?那么当然就要讨论什么时候用C而不用C++啦。

一直以来都严格遵循OO的原则来进行开发(用的工具是C#和Qt),直到最近,开始接手某同事的代码,整个项目20多个小工程(代码量并不多),除了界面部分用了MFC这种不伦不类的OO以外,所有的代码都是C写的。但是模块化做的非常好。后来跟他讨论为何不用C++,他说其实没有什么特别的,就是习惯和爱好而已,后又补充:

如果不用多态的话,其实不管怎么写,不管用那种语言写,都算不上真正的OO

忽然觉得很有道理……

其实这是一个好问题,

题主开始欣赏到纯 C代码所带来的 “美感” 了,即简单性和可拆分性。代码是自底向上构造,一个模块只做好一个模块的事情,任意拆分组合。对于有参考的 OOP系统建模,自顶向下的构造代码抽象方法是有效率的,是方便的,对于新领域,没有任何参考时,刻意抽象会带来额外负担,并进一步增加系统耦合性,设计调整,往往需要大面积修改代码。

有兴趣你可以读读《Unix编程艺术》,OOP的思维模式,是大一统的;C的思维模式,是分离的。前者方便但容易造成高耦合,后者灵活但开发开发太累。用 C开发,应该刻意强调 “简单” 和 “可拆分”。一个个象搭积木一样的把基础系统搭建出来,哪个模块出问题,局部替换即可。

自底向上的开发模式,并不是从不站在大局考虑问题,而是从某个子系统具体实现开始,从局部迭代,逐步反思全局设计,刻意保持低偶合,一个模块一个模块的来,再逐步尝试组合。

自底向上强调先有实践,再总结理论,理论反过来指导实践,又从实践中迭代修正理论。这和人类认识世界的顺序是一样的,先捕猎筑巢,反思自然是怎么回事,又发现可以生火,又思考自然到底怎么回事情。

它的反面,是指大一统设计,你一开始用 UML画出整套系统的类结构,然后再开工设计。这种思维习惯,如果是参考已有系统做一个类似的设计,问题不大,全新设计的话,他总有一个前提,就是 “你能完整认识整个大自然”,就像人类一开始就要认识捕猎和筑巢还有取火一样。否则每次对世界有了新认识,OOP的自顶向下设计方法都能给你带来巨大的负担。

所以有些人才会说:OOP设计习惯会依赖一系列设计灵巧的 BaseObject,然而过段时间后再来看你的项目,当其中某个基础抽象类出现问题是,往往面临大范围的代码调整。这其实就是他们使用自顶向下思维方法,在逐步进入新世界时候,所带来的困惑。

当然也有人批判这种强调简单性和可拆分性的 Unix思维。认为世界不是总能保持简单和可拆分的,他们之间是有各种千丝万缕联系的,你一味的保持简单性和可拆分性,你会让别人很累。这里给你个药方,底层系统,基础组建,尽量用 C的方法,很好的设计成模块,随着你编程的积累,这些模块象积木一样越来越多,而彼此都无太大关系,甚至不少 .c文件都能独立运行,并没有一个一统天下的 common.h让大家去 include,接口其他语言也方便。

然后在你做到具体应用时根据不同的需求,用C++或者其他语言,将他们象胶水一样粘合起来。这时候,再把你的 common.h,写到你的 C++或者其他语言里面去。当然,作为胶水的语言不一定非要是 C++了,也可以是其他语言。
————-
PS: 这里主要在探讨 OOP存在的问题,并没有讨论嵌入式这种资源限制的情况,以及操作系统和底层等需要精确控制硬件和内存的情况,更没有讨论 C++在语言设计层面的事情。

————-

转部分答疑:(点击more展开)

Q:“实际上是,如果你能清晰的知道你要做什么事情,那C很好。但如果你只能确定流程基本是对的,而很多系列可能在后续维护中不断更改,或者增加更多的支持,那c的overhead就很大了。当工程非常庞大的时候会很难维护。比如开发一个数学算法库,其实跟数学没有关系,就是一个大数(任意精度)的一系列计算程序。这个程序可以是没有GUI的。开始自己设计一个大数的运算内核,然后还有很多更高级的计算算法。将来有一天想把内核替换成GMP库,或者用户可以动态替换自己的内核”

A:就以你说的大数计算为例,大数计算底层驱动根据CPU更换函数指针是个不错的选择,见polarssl openssl,我还真建议你用C来写大数的底层,因为你今天要算个求幂取模,明天要算个gcd,后天要生成质数,你无法预测你的大整数里面究竟有多少个接口,这时候用c的分治思想就很合适。大数不是一辆飞机,它会飞,会降落,会拐弯,这都是飞机的主动行为,主动行为是有限的,确定的,适合oo的。而一个数字,它几乎没有啥主动行为,相反全是无限的,不可控的被动行为,正合适塞到不同的.c文件中。这种时候你想刻意在一个大数类里设置满无限的方法是不合适的,不该oo的。况且你要夸语言,大数基础库用c接口到其他语言方便。所以你会看到openssl polarssl到其他语言的很多绑定,可你从来不会看到crypto++除了c++外被导出到其它任何语言了。在你用C实现了大整数基础功能并导给其它语言接口后,针对c++用户,专门包个class的壳,选择一些基础方法放进去,给cpp用户提供点方便。下面核心算法变了,比如你实现了一个sse版本的乘法,运行时换函数指针即可,外层完全不可见,多好!polarssl中还用了一些宏来代替为数不多的几处用模版很方便的地方。

704 total views, 3 views today

I like thisUnlike Like4I dislike thisUndislike 2

Categories: 编译技术, 随笔 Tags: C++

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK