1, C++ 的功能基本都能用 C 实现
很多人认为和 C 相比, C++ 的主要有点是可以实现面向对象的程序设计. 但实际上, C++ 中大部分面向对象的功能都可以用 C 来实现. 例如用 struct 代替 class, 用 struct 中首地址包含 struct 来实现继承, 用增加的显式 this 指针来实现 对象的成员函数, 用函数指针来实现虚拟函数和多态, 用宏定义实现模板. 事实上, 早期的 C++ 就是用 C 实现的. GLib 中的 GObject 就是一个用 C 实现面向对象的例子. 另外, 可能很多人喜欢用 C++ 的 STL 标准模板库, 但用 C 也有类似功能的替代品, 比如 GLib.
2, 累赘的缺省构造函数
对于对象的构造, C++ 的构造函数使用了很多缺省机制, 表面上的目的是减少用户的代码量和为了与 C 兼容. 然而, 在实际设计时, 往往这些缺省的机制无法满足用户需求, 从而使用户不得不改写这些缺省机制. 此时这些缺省机制就成为用户的累赘.
这点在构造函数中体现的尤其突出. C++ 为每个类设置了缺省的无参数构造函数和拷贝构造函数, 这一行为 与 C 中对 struct 的处理是兼容的. 而对于实际中的复杂 struct, C 中的缺省无参数初始化和缺省拷贝 由于很难满足用户的需求, 几乎很少被用户使用到. C 用户会根据自己的需要提供一个显式的 初始化或拷贝函数. 对于不需要拷贝的对象, 例如仅包含一些回调函数接口的对象, 可以不提供拷贝函数, 因为用户很清楚这种拷贝不会发生. 然而在 C++ 中, 无参数构造函数和拷贝构造函数却会在很多情况下被隐式的调用. 例如在 New 一个数组, 或是使用 STL 模板库时, 这使得开发者不得不花费很多额外的精力来处理或思考根本没有用处的默认行为, 并增加很多垃圾代码.
3, 继承机制中构造函数的调用顺序与需求不符.
另一个累赘的行为体现在继承机制中, 也与构造函数有关. 当一个 C++ 对象在构造时会先调用所有继承基类 和包含成员的构造函数, 然后再调用自身的构造函数. 这一隐式行为看似很合理, 但实际构造对象时, 却经常遇到需要先对构造函数的参数进行某些判断或处理, 而后才能初始化基类或成员的情形. 在该情形下, 隐式行为就成为累赘, 用户还是要在自身的构造函数中显式构造基类和成员对象. 此时我能想到的办法只有在基类或成员对象所属类中单独再定义一个普通函数来实现构造对象的功能, 而后在用户新定义的构造函数中显式调用上述普通函数, 这等于是完全绕过了 C++ 的构造函数机制, 还是相当于用 C 的机制在实现面向对象.
4, 多余的数据保护机制
和 C 相比, C++ 提供了 public, protected 和 private 三种数据访问级别, 以及通过友元局部提升数据访问级别的机制. 表面上, 这一机制是为了增加代码的安全性, 但在实际设计中, 很难在设计一个数据结构(类)时就确定该类会被如何使用. 所以经常会出现用户将设计时被标记为 private 的成员在使用时重新修改为 public, 或者单独再为 使用者编写一个读写该成员的成员函数. 当然, 和直接访问相比, 使用成员函数访问对象的属性可以避免一些非法访问, 从而增加程序的安全性. 然而, 实际上这类非法访问是应该在程序设计时就避免的, 而非通过增加代码来检测这种访问. 这种额外的检测不仅使代码变得累赘, 更影响执行效率.
5, 其他,
还有一些其他的特性是 C 具备但 C++ 不具备的, 比如在函数内任意位置声明以变量为大小的数组, 在 C++ 中只能通过 new 和 delete 实现, 但在 C 中却可以绕过 malloc 和 free, 从而实质性的简化代码.