`
helloyesyes
  • 浏览: 1306839 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

被误解的C++——C++的缺陷和D的缺陷

阅读更多

C++的缺陷和D的缺陷

D语言,从字面上讲应当是在C/C++的基础上进了一位,其特性当然也进了一位。真是这样?也是,也不是。这得看你的出发点和价值观了。

D的定位在于继承C/C++的优势,但却更加易学易用。这种定位招人喜爱。C/C++的致命伤就在难学难用上。(不少人认为C++难学易用,我也持这种观点。但是既然要拿来同D比较,那我也只能跳一回票。只此一次,下不为例)。

我大致了解了一下D的特性,初步混了个脸熟。D差不多通过以下方式达到其目的的(如有错误或遗漏,请轻轻扔砖J):

<!--[if !supportLists]-->1. <!--[endif]-->新的语法,消除了一些缺陷,比如C++模板的>>问题(对此我持保留意见,一会儿再探讨)。同时,也优化了编译,提高了编译速度;

<!--[if !supportLists]-->2. <!--[endif]-->将字符串、数组等内置,使其得到编译器的充分支持;

<!--[if !supportLists]-->3. <!--[endif]-->module代替头文件,抛弃了来自远古时代的遗物;

<!--[if !supportLists]-->4. <!--[endif]-->用内置特性实现C++的一些库功能,比如traits

<!--[if !supportLists]-->5. <!--[endif]-->使用gc方便内存管理。但保留RAII功能;

<!--[if !supportLists]-->6. <!--[endif]-->增加编译时计算支持。将C++部分通过模板实现的编译时计算放到内置特性中实现;

<!--[if !supportLists]-->7. <!--[endif]-->增加mixinmp支持。

限于我对D的了解,暂时只想到这些内容。总体上,可以认为,D的改进主要集中在将C++的很多由库实现转移到内置特性实现。内置特性的实现通常具有针对性,在语法上更简洁。此外,D也消减了一些不常用,但容易引起麻烦的特性。并且使一些特性自动化,以减少使用时的压力。

但在我看来,D所做的,是好心没办好事。因为D并没有真正抓住C++的核心问题,也没有很好地认清C++成功的关键。D继承了C++大部分的特性,这表明它认可C++的强大。(不然它留那么多特性干什么)。但是却没有能够从C++身上吸取真正的教训。

首先,C++的语法存在缺陷,Bjarne不止一次提到这个问题。根本原因可能是其语法是non-LR的。这个问题应该不难解决,我相信D已经解决了。但是,D大费周章地采用()!()作为模板的操作符,似乎有欠妥当。首先,程序员的习惯,促使他们不容易接受这个新操作符;其次,()容易同函数参数产生混淆,不如<>来的直观;最后,过度放大<>存在的问题。<>的问题主要有两个,一个是同>>操作符冲突,这个问题在C++0x中已经解决。另一个是<3>4>问题,这种情况相对少见,而且可以利用<(3>4)>加以解决,并非关键性问题。为这些“鸡毛蒜皮”的问题而采用(),似乎不太值得。

其次,C++的最初动机也就是提供“更好的C”。此后,随着需求的不断扩展,逐步加入了各种高级特性。也就是说,Bjarne在最初的那段时间里,并不是那么高瞻远瞩。尽管他为C带来了OOP这种新的编程范型,但是从最后的结果来看,他的思维似乎也不够大胆。因此,C++的很多特性配合不好,有拼凑的感觉。D则站在了巨人的肩膀上,自然有更好的基础,更容易避免C++身上发生过的问题。

再来看D,目前所有的特性和能力,都没有超出原来C++的范畴,只是在其上做一些小修小补。从编程技术而言,相对C++没有显著的进步。不错,D在很大程度上简化了使用,但这是有代价的,它放弃了很多有用但难缠的特性。但这也会带来应用上的局限。

对于系统编程的定位,D似乎把易用性放在了过高的位置。实际上,后面我们将看到,D的面前同样存在另一条路可走。这条路,可以在C++的基础上提供更好、更强大的特性,但却可以消除很多C++的不足(但不是所有的,很多问题算不上缺陷,只是某种强大功能的副作用。强大的功能人人都想要,对吧?那就忍着点吧)。

再次,C++的很多问题来源于它逐步堆砌的特性。而且这些特性又是来源复杂,缺乏一致性。由此,很多first-class的概念被迫使用非first-class概念实现。

在这方面,D则做得更糟。比如,D强化了gp,但却没有引入concept,将来是否会引入,尚不得而知。这使得D无法根本上消除gp中遇到的诸多问题。相反D试图通过static ifstatic dispatchC++常用的手段)之类的second-class特性完成concept这种first-class的需求。显然没有从C++中获得教训。

D象其他语言那样,把数组和字符串作为内置类型处理。在表面上,这是进步,用first-class的概念,用first-class的类型表示。但在我看来,这是一种倒退。说清这件事,就得看看什么是first-class的。传统上,类型分为内置类型和用户定义类型。而内置类型被界定为first-class的,得到编译器的优先照顾。然而,内置类型是否真的first-class呢?不,有比内置类型更first-class的,那就是类型。迷糊了?我慢慢说。

无论内置类型,还是用户定义类型,都是类型。我们传统上将他们区别对待。实际上,划分内置类型和用户定义类型的一个理想上的标准是原子性,即如果一个类型,(在语义上)不能分割了,便作为一个内置类型处理。为了保持移植性,象int之类的类型,被作为原子类型,而不考虑它内部字节的排列。同样stringarray也是如此。但是,在很多系统中,比如SQL,为了处理方便,很多非原子性的类型也作为内置类型处理,比如datetime。这就表明,现实中内置类型和用户定义类型并没有严格的界限。

C++创建时有一个宗旨:让用户定义类型象内置类型一样处理。这句话是非常first-class的。也就是说,把所有类型一视同仁。如果能够做到,那么这门语言中,将只有类型这样一个first-class的概念,而不再分类。那这样得到的好处是什么呢?就是灵活性、扩展性、简洁性。

下一个问题,哪种类型是最first-class的?在C++中,内置类型是最first-class的,D也是一样。但事实上,最first-class并非内置类型,而是字节。汇编语言中,所有类型都可以看作字节或字节序列。也就是说,任何类型,无论内置类型还是用户定义类型,都是由字节组成。在此基础上,我们便可以用字节定义类型的静态结构,包括内置类型。也就是说,在静态结构定义上,内置类型完全可以同用户定义类型同等处理。

但是,内置类型和用户定义类型的行为是不同的,因为编译器并未把他们作为同一样东西处理。此时,我们便可以看到C++那句豪言壮语的作用了。当用户类型能够像内置类型一样的情况下,内置类型和用户定义类型将不做区分,完全可以一样处理。这便可以导致一个结果,就是所有类型,包括内置类型,都可以通过库而非编译器实现。于是,语言便可以扩展出丰富的“内置类型”。(呵呵,是不是有点metaprogramming的味道?DSL听了肯定高兴)。

C++朝这个方向努力做了,实现了绝大部分目标,但也未能100%地实现。比如,智能指针尚无法做到象内置指针一模一样的行为。(C++0x引入三个cast操作符重载后,情况就会好很多)。

这一点上D走出了一步,但却没能乘胜追击。D为所有类型,包括用户定义类型都定义了一组properties,用于获取类型的特性。在这一点上,所有的类型都一视同仁。但是,之后却依旧按传统将内置类型和用户定义类型隔离,并且将数组和字符串放回内置类型。再加上操作符重载上的一个明显退化,便坐失了统一类型处理的优势。

操作符重载,是使用户定义类型得以同内置类型一样处理的关键。C++除了少数的几个操作符外,其余都可以重载。这带来了极大的灵活性,(尽管如此,也未能使所有类型统一处理),但也带来了一些麻烦。(操作符重载带来的语义上的问题,不应该由语言负责,除非不想获得操作符重载的好处,否则必须承担此中的风险)。D缩小了重载的范围,避免一些混淆,以达到简化学习和使用的目的。(C#也是如此,但我并没有看到有多大的效果,我们重载操作符通常都集中在某些常用的操作符上,多数的操作符重载不会成为困扰我们的原因)。

D在操作符重载上的一个明显的变化,就是不使用操作符本身作为重载的定义,而是用等价的字符表示:

//C++

A operator+(A const&, int);

//D

class A

{

A opAdd(int v) {…}

int opPos(){…}

};

这种方式存在它的好处,但同时也带来了不足。好处是可以明确地区分统一操作符在不同情况下的语义,看起来非常明确。但是,这种方式使用起来却不如直接使用操作符本身来的直观,使用者必须识别这些操作符和对应的函数,这增加了记忆的负担。而operator+则侧重于记忆少数规则,理解规则,便可以举一反三,快速运用到其他操作符上。同时增加的这些内置函数记号,消耗了宝贵的关键字。两者的是非得失,全凭使用者的喜好,算不上什么天大的问题。只是我更喜欢记忆规则,而不是具体的记号。

D的操作符重载真正的问题在于取消了自由函数的重载形式。这样似乎简化了学习和使用,但实际上,只会把问题复杂化。成员型的操作符重载并非操作符最本质的形式。将操作符重载完全局限于成员函数,使得操作数无法交换。于是D通过允许定义opxxx_r()程序函数定义交换操作数的版本(复杂了吧)。对于另一个操作数类型,是否定义相应的操作符重载呢?这种方式迫使程序员不断地考虑这类问题。如果合作开发,那么必然增加了程序员间协调的负担。

无论如何,二元操作符的形式更接近自由函数。用自由函数加以表达,更加直观和明确,而且更便于集中处理。这篇文章,很好地阐述了过多的成员函数对封装性产生的影响。成员函数的一个好处是可以访问非public成员,但就像其他成员函数一样,成员型的操作符削弱了类型的封装性。如果不访问非public成员,那么作为成员,没有任何意义。

操作符重载是一个first-class的特性,但是D却使用了非first-class的形式表达。作为对C++操作符重载的简化,我认为合理的形式应当是:赋值操作符(=)、类型转换操作符,以及所有一元操作符,都采用成员的形式。二元以上的操作符都采用自由函数的形式。这样,规则并没有复杂多少,但却使得不同的操作符重载都能够以first-class的形式表示。

first-class特性的问题上,另一个问题是D引以为豪的traitsTraits是非常有用的东西,使我们静态地获取类型的特征。traitsC++中起了非常重要的作用。所不同的是,C++通过类库的形式提供traits,而D则采用编译器内置特性提供。相比之下,受制于语言本身的特性,类库的实现难以提供完全的类型信息。而编译器内置特性,可以提供更完全的内容。

但是traits毕竟是非first-class的特性,C++0x通过引入first-classconcept,大大消除了对traits的依赖。具体的可以看这里这里这里。而traits背后真正的机制则是reflection。在这里reflection是真正的first-class机制,而traits是缺乏reflection(编译时)的一种替代或模拟。

D的问题在于,traits成为一种语言特性,当未来引入了conceptreflection之后,它将成为摆设,宝贵的语言特性资源被浪费。如果不引入conceptreflection,那么很多依赖于这些特性的问题无法得到解决,而traits也无法独立支撑这个局面。这会使D在这方面处于进退两难的境地。同样的问题也存在于mixin上。Mixin可以被看作一种Meta-Programming的机制,但并不完全,也非first-class特性。为真正解决现实中的问题,在GPL中引入Meta-Programming的需求越来越强烈。一旦引入真正的first-classMP机制,那么mixin也会处于尴尬的地位。

这些问题表明,设计D的出发点存在问题。D仅仅试图在C++的基础上简化学习和使用,而不是采取更加本质,更加根本,更加first-class的手段来彻底解决C++面临的问题。就是说D并非一种面向未来设计的语言,仅仅关注眼前的蝇头小利。这种思维上的局限性,很容易使D在未来同更强大的语言,不仅仅是未来的C++,竞争的时候,处于不利的地位。因为限制已经造成,围栏已经建好,再想扩展便会受到很大的限制。

我还是那句话,如果D仅仅局限在修正C++的某些问题,那么说明它并没有从C++哪里吸取真正的教训

最后,D似乎并没有充分意识到灵活性和程序员的选择对系统开发的重要性。C++的机巧性、危险性很大程度上是被过度放大了。在现实的开发过程中,我们绝大部分的时间,实际上都在老老实实地使用C++的普通功能,(但请记住,是在高级特性的支援下,使用普通功能)。这些动作都是常规的,成熟的。至于那些复杂、危险特性,则很少使用。即便使用,也是由专人(受过训练的,有免疫能力的)集中运用。在这样一个大环境下,语言的灵活性相比那些很cool的功能而言,更加重要。比如,使用多继承的权利。

多继承的主要问题在于钻石型继承带来的麻烦。但是,这种情况极少出现,通常也只在过度OO的设计中存在。在其他方面,多继承是非常容易处理和使用的。更重要的是,它是非常有用的,有时甚至是关键的(想想policy可以为我们消除多少类、继承和代码冗余,带来多大的灵活性)。为了一种很少出现的情况,把路整个地堵死,对于java这样的高层语言,或许可以忍受,但对于系统级的语言,是难以接受的。(至少对我如此)。更何况编译器可以准确地识别钻石型继承,并作出自动化处理(默认virtual继承),除非有特殊需要。

系统级语言不仅仅要求能够完成程序。鉴于系统开发的广泛性、灵活性,以及扩展性要求,语言的灵活性和程序员的选择是非常重要的。作为高附加值的开发任务,系统开发对于语言的复杂性的容忍能力是非常强的。

C++的问题是过于灵活,比如缺省情况下单参构造函数执行隐式类型转换,在应用中造成无数问题。一门新的语言完全可以通过合理地限制这种灵活性,消除问题。

我并不是说,C++那些缺陷和复杂是正当的。我只是想表明,通过消减语言特性,抑制语言的灵活性,限制使用者的选择,不是简化语言使用的正当手法。一种系统语言,应当以更加根本(本质)的方式解决C++的问题。在这一点上,D做的并不是很好。(就像跑步比赛,如果只把目标定在第一个人的身上,那么终究无法将其超越。只有把目标定在第一个人前面,才能得到冠军)。当D只专注于宣扬C++的缺陷,以及它所给出的解决方案时,便注定它无法成为超越C++的语言。在我看来,它应当把更多的精力花在如何提供更多first-class特性上,而不是用来标榜自己的那一点点进步。

分享到:
评论

相关推荐

    掌握Visual C++——MFC程序设计与剖析书附光盘

    提供了掌握Visual C++——MFC程序设计与剖析书的源代码

    从缺陷中学习C/c++

    从缺陷中学习C/C++:探索编程陷阱与成长之路 ...正如文章所述,“C/C++的伴侣”——那些令人头疼的缺陷,实际上是我们成长道路上不可或缺的伙伴,它们促使我们不断进步,最终掌握这门语言的精髓。

    一个好玩的c++游戏——生死枪战!

    生死枪战游戏目前以3个模式运行(持续更新),玩家可以通过自己的喜好来选择,如:新手训练营,无限...如果想要更多c++游戏,请进入zzz工作室,网址http://zzz07.ysepan.com/ 我的聊天室网址:https://hack.chat/?zzz

    从缺陷中学习c++

    本文档试图从一个独特的角度出发——即从缺陷中学习,以此来帮助读者更好地理解和掌握C/C++。 #### 缺陷之一:内存管理不当 C/C++最著名的特点之一是程序员需要手动管理内存。这既是优点也是缺点。优点在于程序员...

    C++思维导图Xmind文件和.png文件(持续更新)

    C++思维导图Xmind文件和.png文件: 构造函数与析构函数思维导图Xmind文件和.png文件 拷贝构造思维导图xmind文件和.png文件 运算符重载思维导图xmind文件和.png文件 初始化列表、匿名对象、static成员、类的隐式类型...

    C++——复数——运算符的重载

    对复数进行运算,主要是练习运算符的重载技术。

    基于Qt5+OpenCV3+C++实现的PCB缺陷检测系统+源码+文档(毕业设计&课程设计&项目开发)

    基于Qt5+OpenCV3+C++实现的PCB缺陷检测系统+源码+文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于Qt5+OpenCV3+C++实现的PCB缺陷检测系统+源码+文档,...

    C++——字符串变量.txt

    处理字符串,除了字符数组,还有用C++的string类型来定义一个字符串变量(对象)。 定义字符串并进行初始化: string string1="chance"; 注意:必须要有头文件# include ; 比较:字符数组不能直接对字符变量进行...

    c++——扫雷简单版9*9

    c++实现扫雷

    c++——万能头文件。

    万能头文件 万能头文件就是一个包含几乎所有的头文件,如: #include #include #include #include #include &lt;cmath&gt; #include &lt;deque&gt; #include &lt;vector&gt; #include &lt;queue&gt; ...#include &lt;bits/stdc++.h&gt;

    C++编程惯用法——高级程序员常用方法和技巧

    在C++编程中,掌握高级程序员的常用方法和技巧是提升代码质量和效率的关键。...在阅读《C++编程惯用法——高级程序员常用方法和技巧》这本书时,结合实际项目进行练习,将有助于更好地掌握这些技巧。

    超越c++标准库——boost程序库导论

    总的来说,《超越C++标准库——Boost程序库导论》这本书详尽地介绍了Boost库的各种组件及其用法,是深入理解C++和提升编程效率的重要资源。通过学习和应用Boost,开发者不仅可以提高代码质量和性能,还能掌握更多...

    易学C++——初级教程

    适合初学者的程序设计教程,希望有助于爱好或者正在初学C++的朋友。

    C++资源——实验讲义

    《C++程序设计讲义——第 14 章 C++文件操作》 在C++编程中,文件操作是一项核心技能,它涉及到程序与外部数据的交互。本章主要探讨了C++中的文件基本概念、文件分类、文件指针、与文件处理相关的类以及文件的打开...

    C++程序——画图板

    本项目名为"C++程序——画图板",其核心是利用C++来实现一个简单的画图工具,让用户可以在界面上绘制图形。下面我们将深入探讨这个项目的相关知识点。 首先,我们要理解C++中的图形用户界面编程。在C++中,可以使用...

    C++程序设计教程——设计思想与实现习题代码答案

    《C++程序设计教程——设计思想与实现习题代码答案》是一本面向初学者和有一定基础的程序员的教育性资源,旨在帮助读者深入理解和掌握C++编程语言。这本书结合了作者两年的教学实践经验,使得内容既理论严谨又贴近...

    C++课件完整——包含C++全部内容

    这份"C++课件完整——包含C++全部内容"的压缩包文件,提供了全面的学习资源,适合初学者和进阶者深入理解C++的核心概念和技术。 首先,我们从文件名中可以看出,这些课件覆盖了C++的基本到高级的主题: 1. **第二...

    C++课设——图书管理系统

    C++课设——图书管理系统,供大家一起共同分享学习。

Global site tag (gtag.js) - Google Analytics