Skip to content

C++ Overview

C++是一门高性能的编译型语言,广泛应用于多种领域,系统软件、游戏引擎、服务器后端、嵌入式系统、银行系统等等。
它是由Bjarne Stroustrup于1979年在贝尔实验室发明的。最初被称为“C with Classes”,后来在1983年被正式命名为C++。

如大多数软件作品的发展类似,软件作者会使用现有的一些轮子来进行实验和PoC制作。早期的时候Bjarne使用C的宏机制来实现面向对象的特性,最终再使用C的编译套件来完成编译工作。 具体的故事和心路历程可以参考Bjarne的著作《C++语言的设计和演化》(The Design and Evolution of C++)。

没有所谓的正宗C++风格

C++支持多种范式(Multi-Paradigm)编程:

  1. 面向过程式编程
  2. 面向对象编程
  3. 泛型编程
  4. 函数式编程
  5. 并发编程 - C++11中引入了线程库,支持并发编程。Go、Erlang这种属于语言级别的支持并发编程。
  6. 元编程 - 编译期执行代码
  7. 模块化编程 C++20中引入了模块化编程,用来改善传统的头文件机制,类似nodejs、python等语言的模块化机制

Bjarne在其书中提到,他希望任何人都可以在C++中找到适合自己的编程风格。因此,请卸下包袱,选择个人喜欢的编程风格,没有所谓的正宗C++风格一说! 当然,如果是在团队中工作,还是要遵循团队当前的编码规范。

C++的发展理念

语言特性与标准库相结合的发展

Bjarne对于C++的演进比较谨慎,每一个特性必须经过深思熟虑和在C++委员会内部广泛地讨论,才被纳入C++标准中。
有一些非语言级别必需的,但是开发者又特别想要的特性,委员会会倾向于在标准库中添加相应的支持。 因此,虽然大家都在说C++变得越来越复杂,其实语言本身并没有太多的变化,更多的是标准库的扩展。

兼容性

另一个重要理念是向后兼容性

  • 兼容C语言保留直接操纵底层的能力
  • 兼容旧版C++标准编写的程序

零代价抽象

零代价抽象 Zero-overhead Abstraction。你不需要为你没有使用的抽象机制付出任何代价。
如果你不使用面向对象编程,你就不会为虚函数表付出任何代价;
如果你不使用异常处理机制,你就不会为异常处理付出任何代价;
在C#中,你没法选择不使用垃圾回收机制。

与C的对比

我觉得C语言就像《第一滴血1》兰博的军用匕首。它小巧锋利,兰博可以用它做任何事情。前提是你得是兰博,你知道你要做什么,你也知道怎么做,会有什么困难,以及怎么克服这些困难。

C++更像是一把瑞士军刀,里面有多种现成的工具,适合不同场景。 大多数时候,你只使用其中的一两种工具(C++的子集),你对它们也最熟悉。 在你遇到其他问题时,你得知道应该用哪一种工具。

因此,最好的办法不是精通C++所有的特性,而是对C++整体有个了解,知道它有哪些“工具”,等到真正需要用到的时候,再去深入学习。

抱怨和问题

除了C++的复杂性经常被诟病以外,C++如同C语言一样也会有一些安全问题,C++程序也会出现内存泄漏、内存访问越界等问题。
现代C++已经做了很多的改善,通过RAII、智能指针、标准库等机制,极大减少了相关的风险。

编译速度慢。除去头文件机制带来的影响,C++的模板机制会在编译期实例化模板代码,生成对应的目标代码,这会增加编译时间。

ABI兼容性问题。 C++的名字修饰(Name Mangling)会导致不同编译器生成的目标文件不兼容。 当制作库文件发布之后,如果对外公开了类的声明头文件,后续对于类的修改,也可能会导致ABI不兼容(比如调整了类中接口的顺序,会导致vtable的布局变化)。

C++没有统一的包管理器,Python、Rust等语言都有标准的包管理工具。 当然,现在可以使用conan、vcpkg结合CMake来进行包管理和导入。

C++就像我们自己写的程序,你很难对一个仍然在大范围使用的软件系统进行推倒重来,纵使有无数的理由和现代化的理念。
如果说系统架构意味着某种程度上的妥协,作为基石的语言设计更是如此。更何况C++仍然保持了旺盛的生命力在不断地演进和完善。

c++的表达能力和复杂性

在用C++设计系统的时候,往往心智负担会比较重。担心自己的实现不是最符合C++设计的,担心是在用C++的环境编写C风格的程序,继而一度使用C和C#来开发。

在使用C#这一类语言时,开发人员会更聚焦在系统的复杂度分解,再到类之间的关系,进而考虑类本身的设计。
而当使用C++时,在类的实现语法层面需要考虑地更多,explicite、类的6大函数,copyablenoncopyable等细节都要在大脑里盘点一遍。

举例而言,类的静态变量,在C++中还需要单独处理,属于实现上的额外要求。

1
2
3
4
5
6
class A {
public:
    static int count;
};

int A::count = 0;
1
2
3
4
class A 
{
    public static int count = 0;
}

在标准库不断丰富的时候,C++的表达能力也在不断增强,promise、future等现代编程语义也被加入到“瑞士军刀”库中。 C#每一次SDK大版本升级都会带来诸多语法糖的变化,来进一步降低开发人员的心智负担,语法上朝着人类自然语言方式发展。

C++在语义层面可能会有歧义,比如virtualfinal同时修饰一个接口在语法上是合理的,但是语义上会让我比较困扰。

Bjarne Talks about the C++ language

How to learn C++, what is the best strategy to learn C++

I think first you need an overview of the language.
A lot of students dig in and want to know everything about some little detail. But the point is , they can't understand it until they have a broader view of the language.

As people go in and they see, oh, pointers.
Great, I want to know everything about pointers, and why would you want to do that?

They should be work together with classes and other data types, and how do you do a traversal or a container and such.

You shouldn't obsess about one little thing, or they go in and they say, well, there're 32 different basic arithmetic types. It's probably right. I can't remember.

I mean, that's just the sew of little type or rules for conversion. Most of the time, I don't use all of those types, and most of the time, I don't have to think about those things.

So instead of digging into the little details, pointers, how to do a class, hierarchy, things like that, get an overview.

What should students focus on when learning C++?

I think first of all they should try and look at C++ as a modern language.

Far too many have learned C++ as if it was just C or as if it was still 1985, and that pains me a lot because it's much better these days.

You look at it and you see how to use the abstraction mechanisms, you use the libraries.

They are the standard library which is fundamental data structures, few algorithms, timing, regular expressions, that kind of stuff.

Then there are specific libraries for specific areas, the database interfaces, you have specific graphic libraries, animations libraries, and such.

So I think when you start programming, you stick to the fundamentals for a while, because well , the fundamentals are the fundamentals.

You have to know your algorithms, you have to know your data structures, you have to know a little bit of machine architecture if you want to write at that level.

Then you're going to actually do something. If you're writing video game, you have to know what game's engine and the libraries that go in there.

So you go through the fundamentals and then something specific to your interest.

编译型语言vs解释型语言

编译型语言会在编译阶段将错误或者潜在的风险提示出来;
相反动态类型语言可能会在运行时期报错,但是这时候用户是无法处理这些问题的,因为他们很可能不是开发人员。

编译器是开发人员的好帮手,可以生成代码,转换代码,也可以发现风险。

编译型语言的优势也是劣势,相比动态类型语言,我们需要先通过编译器生成目标平台上的可执行文件,再将该文件发布给用户;因此,为了支持多种平台,我们还需要针对每个平台编译生成对应的目标文件,再将其提供给用户。甚至还需要为了目标平台而去构建交叉编译工具链

还有一个优势,编译型的语言生成的是最终可执行的目标文件,因此运行效率高

动态类型语言(比如python,JavaScript等)拥有很好的跨平台特性以及容易上手等特点。

因此,在web开发、系统管理等方面非常适合使用这一类型的语言,当下他们也获得了蓬勃的发展。

还有一种场景适合动态类型语言,当目标场景是交互式的时候,此时效率不是那么重要。比如AI和数据科学实验时,使用Jupyter Notebook进行即时开发,用户无需频繁地等待编译器处理&执行修改后的代码这个繁琐的流程。

动态类型的语言往往运行效率比较低,因为大多数是解释型语言,在运行时依赖解释器逐行将代码转换成目标系统的指令来运行。