关于C++语言的一些思考

引言

诞生之初,C++语言不仅通盘继承了C的语法,而且在源代码层级保留了对C的兼容性。于是,老的C程序员们比较开心,对于他们来说,这门新语言看上去不是那么地难上手,何况还能原封不动地重用现有的C库,所以,C++的推广过程似乎蛮顺利,很快,声称转向C++的程序员开始有意地把类加到代码里,尽管如此,这门语言却从来没有实质性地深入人心,至少从C++设计者的角度讲是这样的。
笔者的结论是:良好的初衷有时可导致难以弥补的后果,即C++始终无法作为一种全新的语言被认可,结果某种程度上沦为C的扩展。不信瞧瞧代码里的那些类,好多连个拷贝构造函数都没有,他们不过把它看成是种更好的C语言而已,这种先入为主的羁绊严重地阻碍着C++的特性得到充分发挥,最终使其逐渐式微于面向对象语言的丛林之中,艰难地挣扎。

看一段代码:

如果遵循C++的设计理念,代码看起来应该是这样的:

但是,以上修改极易遭到非难:许多公司的编码规范中明确禁止使用异常,至今国内许多程序员(尤其是老程序员)对STL的使用还心存疑虑,刚转过来的新手则会质疑代码的性能…… 总之,历史的包袱致使许多C++的特性没有得到广泛的认可,反过来又导致这些特性的底层支持(如编译器)得不到完善,从而将这一语言推入恶性循环,不仅难以实现自我进化,还要面临衰退的危险。 有时在想,当初若是将之命名为D,其演进历程或有大不同也难说。

一、对象构建的陷阱

对于使用类的面向对象语言来说,构造函数的主要作用是实现新建对象的初始化。C++将这一过程藏到了幕后,所有显式的变量定义——不管是全局的还是局部的、所有隐式的变量定义,如函数参数的值传递和函数的返回、以及使用new操作符新建变量,只要该变量属于一个类,则必然有相对应的构造函数在某一刻被自动调用。考虑到对象组合和类的继承引入的双重金字塔式结构,这一调用过程又必须以层次化的形式展开,即从向父类开始上溯,逐层初始化,然后依次构建各成员对象,最后才回到到自身的构造函数中。这是大部分面向对象的语言广泛使用的策略,但不幸的是,技术上讲,构造仍然是一个函数,至少C++没有建立特别的规则防止构造函数被滥用,于是乎总有人忍不住写出以下的代码:

正因为没有编译错误,程序员很难不把构造函数看成普通函数,有些人喜欢用它来做初始化,还有人会做更多,却忘掉了还有一条隐含的军规:构造函数的唯一作用是初始化对象成员,以便将之置于一个预期的合法状态。

正因如此,C++构造函数的执行环境实际上存在着某些隐含的预设,忽略这些预设有时会导致代码运行的结果完全超出程序员的预想:

代码段之一在构造函数中使用了一个虚函数,设计者期望该调用的效果和通常的虚函数调用一样,可以依照对象类型的不同产生不同的效果,但实际的运行结果不尽人意,_Setup()仍旧被绑定在父类的实现上,因为构造过程中的对象其实是不完整的——比如还没有建立虚函数表,所以成员函数调用的动态绑定就凭空消失了。
代码段之二在构造函数中使用了一个全局对象std::cout,但由于无法预知Logger实例的构建时机,也就无法预知std::cout对象在被调用时是否存在,因此,这段代码的运行结果是不确定的。以Ubuntu上的gcc 4.8.4为例,如果使用命令行”g++ main.cpp Logger.cpp”编译,代码可以正确运行,但如果使用”g++ Logger.cpp main.cpp”,代码便会崩溃。

出于对象实例多样化的初衷,C++允许构造函数像普通函数一样传参,也支持构造函数过载(overload), 却没有强制保留缺省构造函数。这一设计显然不利于不支持空引用(或空对象)的C++,首先,new T[]操作不再通用——你无法实例化一个对象数组给没有缺省构造函数的类,然后导致标准模板库变得也不再通用,本质上这成了一种设计的不一致:

显然,std::map的设计是对象的存在为假设的:[]操作符用给定的键值在map中查询,如果对应对象不存在就返回一个缺省对象,这一思路同C++类可能不支持缺省对象的规则是相悖的。

笔者理想中的构造函数应该是这样的:

首先:禁止构造函数调用函数;
其次:强制构造函数的各个参数提供一个确省值。

大神们要求程序员具备跨越陷阱的能力,程序员却希望大神把陷阱圈起来。

二、标准模板库——沉重的技术债

右值和转移语义的实现是C++ 11更新中最值得注意的新特性之一,归根结底是希望还上那笔技术债。在设计之初,标准模板库即被定义为某种容器(Container),无论是vector还是list,其中的元素均为对象,所以为了提供范类型的支持,又不得不使用模板,结果导致无法隔离接口与实现,这其实是与面向对象设计原则相背离的。如果以Java中实现同样功能的集合框架(JCF)为参照,标准模板库的笨拙和死板就显而易见,比如在使用模板库的时候,略有经验的程序员都会把指针作为容器的元素而不是对象本身,vector和list也常常是出现在某个统一列表接口的实现中。如上文提到的,在C++发明早期,许多公司干脆杜绝使用模板库——他们宁愿重新实现自己的数据结构。也是随着硬件计算能力和存储容量的飞速提升,模板库所带来的额外开销才渐渐地可以忽略不计,然而,对于某些资源受限的系统中——如基于微控制器的嵌入式单元,C++仍旧很难替代C,其中一大障碍便是标准模板库。

anyShare分享到:

Meta

cli Written by:

Be First to Comment

Leave a Reply