现代 C++20 实战高手课
卢誉声
Autodesk 首席工程师
3781 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 29 讲
现代 C++20 实战高手课
15
15
1.0x
00:00/00:00
登录|注册

01|Modules(上):C++模块化问题的前世今生

你好,我是卢誉声。
今天是第一讲,我们会从 C++20 中的核心特性变更——Modules 模块开始,了解现代 C++ 的编程范式、编程思想及现实问题解决方法上的革新。
我们都知道,无论是编写 C 还是 C++ 程序,总少不了使用 include 头文件机制,这是 C/C++ 编程中组织代码的最基本方式,用法简单、直接,而且符合直觉。但很多人不知道的是,其实 include 头文件机制不仅有坑,在编程过程中还会出现一些难以解决的两难问题。
接下来,我们就从这个“简单”机制开始探讨代码组织方式的门道,看看里面究竟存在哪些问题,语言设计者和广大语言使用者又是如何应对的,对这些问题的思考,将串联起我们关于 C++ 代码组织方案的所有知识点,也终将引出我们的主角——Modules(课程配套代码点击这里获取)。
首先来看看整个故事的背景,include 头文件机制,它的出现是为了什么?

万物始于 include

作为 C 语言的超集,C++ 语言从设计之初就沿袭了 C 语言的 include 头文件机制,即通过包含头文件的方式来引用其他符号,包括类型定义、接口声明这样的代码,起到分离符号定义与具体实现的作用。
早期,能放在头文件中的符号比较有限,头文件设计是足以支撑系统设计的。但是为了提高运行时性能,开发者也会考虑将实现直接放在头文件中。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

C++20中的核心特性变更——Modules模块,为现代C++的编程范式、编程思想及现实问题解决方法带来革新。文章首先介绍了C/C++编程中使用include头文件机制的基本方式,但指出了其存在的难以解决的两难问题。随着软件技术的发展,C++从C++98过渡到现代C++之后,越来越多的特性可以被定义或声明在头文件中,导致一系列问题的出现,如模糊的模块划分、依赖符号导入顺序、编译效率低下和命名空间污染。 在传统C++编程中,虽然在C++20之前没有统一的抽象模块概念,但开发者总结出了一套较为行之有效的经验,以达到划分业务逻辑代码和提升代码复用性的目的。传统项目中,常用特性包括编译链接、头文件和命名空间来模拟模块。 总的来说,文章通过介绍C++头文件机制存在的问题以及传统C++模块化解决方案,引出了C++ Modules的重要性和必要性,为读者提供了对C++模块化问题的深入了解和思考。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++20 实战高手课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(9)

  • 最新
  • 精选
  • 糍粑不是饭
    老师是否考虑再增加些CMake或者包管理相关的课程呢?😃

    作者回复: Good point! 正好在这里做个调研,大家是否对这个话题感兴趣呢?

    2023-03-18归属地:北京
    4
    6
  • tanatang
    写面向对象的C++,成员和函数都在设计在类中,禁止使用这种不属于任何类的全局函数, 全局变量。

    作者回复: OOP原教旨主义 👍

    2023-01-18归属地:四川
    5
  • wilby
    为什么namespace让符号管理变得很复杂?老师好像就给了个结论,能展开讲讲么?

    作者回复: 文中的意思并非说namespace导致符号管理变复杂。这里的意思是,诸如namespace这类语言特性可以解决符号冲突问题,并且可以在编译时给予友好的提示,但由于C++的编译链接分离机制,C++只为链接定义了一些指导性原则,链接中很多具体技术规范大多由ABI来定义,这就导致在不同编译器和不同体系结构下可能在链接时会遇到不同的问题,这才是C++符号管理复杂性的核心问题所在。而C++ Modules则是更完备的符号可见性管理语言特性,可以与namespace实现互补,而且在链接上也给出了更多的原则(虽然还是无法完全解决ABI问题),让符号管理更加易于理解,更接近现代编程语言,可以一定程度降低整体符号管理的复杂性。

    2023-01-18归属地:瑞典
    2
    5
  • Geek_7c0961
    "这也能大量减少编译单元之间的符号冲突问题,毕竟可能出现,两个编译单元定义了同名,但只想在编译单元内部使用函数的情况,我们并不想给这些函数加上冗长的前缀。那这个时候,只需要使用 static 修饰符。比如我们可以在 A 和 B 中都定义 static 函数 to_int,然后再编译链接,这样就不会出现符号冲突的问题。" 这块儿能否给个具体的代码示例?

    作者回复: 比如以下代码 a.cpp: #include <iostream> static void print() { std::cout << "Print in A" << std::endl; } void fa() { print(); } b.cpp: #include <iostream> static void print() { std::cout << "Print in B" << std::endl; } void fb() { print(); } main.cpp extern void fa(); extern void fa(); int main() { fa(); fb(); return 0; } 这里a.cpp和b.cpp两个编译单元都有print函数,但是函数使用了static修饰符,因此print函数仅对各自编译单元内部可见,所以链接时不会导致符号冲突问题。会正确输出: Print in A Print in B

    2023-01-18归属地:美国
    3
    2
  • 小样
    太可怕了,以前没有想过这种问题。 实际用到的代码规模并不是很大,一个人就能完全掌握各个模块,也就不会有冲突。现在来看传统上符号隔离和污染问题根本就没有在设计上解决过。软件工程规模大后必然有冲突问题。

    作者回复: 是的,所以软件工程需要确定种种规范,就是为了尽量规避这些问题,但在实际的大项目中还是很难完全避免,尤其是要集成大量第三方库的时候会更加头痛。 经验积累很重要,而且很值钱。记得总结出自己的一套实践方法。

    2023-01-24归属地:江西
    1
  • tang_ming_wu
    链接过程,函数地址填充,是不是都是相对地址?

    作者回复: 不一定,C++并没有定义二进制函数地址填充的方式,这完全取决于操作系统与特定体系结构的ABI,有可能是相对地址,也有可能是绝对地址。

    2023-01-17归属地:广东
    1
  • peter
    请教两个问题: Q1:第一个专栏是什么? Q2:实现放在头文件中为什么可以提高运行性能? 文中有这样一句话“但是为了提高运行时性能,开发者也会考虑将实现直接放在头文件中。” 为什么?

    作者回复: A1:《动态规划面试宝典》 A2:因为C++编译期支持针对函数调用的内联优化。 将函数实现放到头文件中,编译期可以根据实际情况对函数调用进行优化,比如将函数调用替换成函数的实现代码,这样针对部分较短的函数可以抵消掉函数调用的性能损耗,这也就是为何明确标为inline的函数定义都一定要放在头文件中。

    2023-01-17归属地:北京
    2
    1
  • 中山浪子
    “ b.o 中会生成一个名为 add 的符号”一般是用什么工具或者怎么查看生成的二进制中的符号?

    作者回复: 可以用binutils中的objdump/nm查看符号,如果是Windows下生成的PE/COFF格式的二进制文件,还可以用VC的dumpbin查看符号

    2024-01-03归属地:江苏
  • 不二
    历史挺好的,但是里面概念太多,解释不清楚感觉消化不了。

    作者回复: 掌握C++的过去,才能更好的理解C++的未来。虽然这是C++编程语言复杂性的一个缩影,但是有助于大家理解现代C++。如果对这些旧有概念感兴趣,可以回顾一下这些概念。有任何问题也欢迎在评论区提出,我会跟你一起探讨。

    2023-01-19归属地:浙江
收起评论
显示
设置
留言
9
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部