C++ 利用条件编译来避免重复引用为什么需要手写?编译器不能自动做这件事情吗?

2022-03-25 13:33:13 +08:00
 vcfghtyjc

继续读《 C++ Primer 》,看到 2.6 中介绍了利用条件编译来避免重复引用。以下是源码:

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
  std::string bookNo;
  unsigned units_sold = 0;
  double revenue = 0.0;
};
#endif

所以理论上来说,是不是每个自定义的数据结构都应该加上这个判断,从而避免重复引用?为什么编译器不能自动做这个事情?重复引用会带来什么样的问题?

3326 次点击
所在节点    C++
29 条回复
GeruzoniAnsasu
2022-03-25 13:36:29 +08:00
说一个会的人都知道但没学过可能无法想象的事:

c++的编译器只会处理「一个文件」,include 的作用是把所有<>里的文件全都拼在一起



字面意思,拼在一起。include 两次就拼两次。
mainjzb
2022-03-25 13:41:45 +08:00
微软干过这种事情.发明了
#progma once

后来人们发现头文件是个憨批设定,严重影响了编译速度。已经在 C++20 里开始推进 module 了。
vcfghtyjc
2022-03-25 13:44:51 +08:00
@mainjzb 为啥头文件“严重”影响编译速度?我以为只有源码长度影响,毕竟头文件一般只是定义并没有实现。
msg7086
2022-03-25 13:45:20 +08:00
重复引用会冲突。
但是自动过滤重复引用以后又等于是扼杀了故意多次引用文件的能力。
所以你只能每次老老实实写上 #pragma once 来告诉编译器你不想重复引用。
GeruzoniAnsasu
2022-03-25 13:46:55 +08:00
能让编译器「自己做这个事情」的提案叫 modules ,是一个有近 10 年历史的卫星,当然这跟 c++存在的漫长时间相比并不算什么

https://en.cppreference.com/w/cpp/compiler_support 在这里搜 modules 有惊喜

p.s. 别看这个 proposal 是 2019 年的,但其实随便一搜就能找到过去的弃案比如 https://isocpp.org/files/papers/n4214.pdf
ho121
2022-03-25 13:57:25 +08:00
如果你真的需要引用两次呢?
Origami404
2022-03-25 14:00:50 +08:00
@vcfghtyjc 因为头文件会展开啊,比如有一个 common.h 被 1000 个 .c 文件 include 了,那么编译器就必须处理这个文件里的代码 1000 次(因为 include 是预处理器指令,它的实现就是直接展开)。如果头文件里再来一点深点的 include ,那就是滚雪球了
mainjzb
2022-03-25 14:01:10 +08:00
头文件比你想象的大的多的多。之前还看到一个 githu 上的神人,推崇库代码全在一个.h 文件里。#include 进来就能用,避免各种编译链接问题。
😅想象一下。a.h 包含 b.h , b.h 包含 c.h 你的文件写了 10 行,引入了 a.h 结果最后编译器导入进来复制了 a.h b.h c.h 的代码,发现有 10w 行。这是在一个没有固态的年代里很容易发生的一件事情。
vcfghtyjc
2022-03-25 14:01:33 +08:00
@ho121 为啥会需要引用两次?引用一次后不就可以编译了吗?
vcfghtyjc
2022-03-25 14:04:04 +08:00
@Origami404 编译器不能 cache 一下每个文件的处理结果,遇到相同的文件直接用之前处理好的结果吗?
shyrock
2022-03-25 14:05:48 +08:00
嗯,所以说锅在头文件。
重复引用什么的都是后话。。。
mainjzb
2022-03-25 14:06:45 +08:00
你说的编译器 cache 叫预编译头文件。事实就是还是很慢。一堆人还在那疯狂用宏写函数啥的。
Cloutain
2022-03-25 14:10:14 +08:00
兼容 C 语言的烂包袱,你看 C#还有这破事儿吗?
vcfghtyjc
2022-03-25 14:11:51 +08:00
@mainjzb 那能不能编译前自动给每个 data structure 都加个判断,除非声明就是要重复引用?
DOLLOR
2022-03-25 14:20:59 +08:00
因为 C/C++里的 include 就是单纯地把其他文件里的代码 copy 到当前文件里进行编译,所以必须手动地写一些指令,防止 copy 后出现冲突。
为什么编译器不能自动做这个事情?人家也想呀,就是所谓的模块化,一直在缓慢推进。
但普及需要时间,就像 JS 的 es module 一样,C++可能会有很长的一段时间,还得用这种原始的方式来 include 文件。
GeruzoniAnsasu
2022-03-25 14:23:47 +08:00
@vcfghtyjc
> 为啥会需要引用两次

你先再看一眼 1L ,然后看这个
https://onlinegdb.com/b4qgRG-ZS


哎,你别说以前可能还真有人认为会有需要 include 两次的地方
neoblackcap
2022-03-25 14:24:47 +08:00
@vcfghtyjc 你说得很好,不要再说了,再说就要请你去写 C++编译器了

因为 C++的编译器继承 C 编译器的特点,可以独立编译文件,这样每一个文件就是一个编译单元,可以分布式编译。编译的时候,编译器是不知道一个符号在全局中有没有被编译的。能知道全局符号信息的那个叫连接器。所以人们为了避免重复编译,所以就在头文件写宏,防止多次编译(不是多次 include )。
因为只要编译了,连接器就能在连接的过程中找到对应的符号的实现。
ipwx
2022-03-25 14:26:03 +08:00
@vcfghtyjc 你说对了,源码长度就是很长,而且还很复杂。

纯 C 语言的头文件一般只有声明所以速度相对快,但是 C++ 不同。为了追求 zero-cost abstraction ,C++ 的很多东西声明和定义全部都在头文件里面,而且模板 meta-programming 对于编译器是复杂的东西。这样叠加上去就,超级慢。一个 C++ 源代码文件 include 展开以后有几百上千 KB ,几百上千个 template class ,我觉得毫不意外。每个源文件都给你来这一套,想想多复杂。

C++20 的 module 就是规定,一些头文件不受其他头文件 macro 的影响。这样你就能预编译这些头文件,就不会有那么多重新编译的开销了。

在 C++20 module 前,业界最佳的实践是 Qt ,大量使用 private class + pointer ,尽量避免使用 template ,达到了很好的编译速度。然而,由于使用这两个技术,所以毕竟不是 zero-cost abstraction ,因此在运行速度上是打折了的。当然比起 node.js 还是秒杀。

最后简要介绍 zero-cost abstraction 。这东西看一个例子:

* C 语言的库函数 qsort 要传入一个函数指针。
* C++ 的库函数 std::sort 传入的是模板函数。

函数指针是不能内联优化的,因此每两个元素比较都不得不进行一次函数调用,有固有性能损失。
模板函数是可以内联优化的,相当于消除了这一次函数调用。更何况消除以后 C++ 可以进一步做指令集优化。

所以 C 语言 qsort 比 C++ 慢。
ipwx
2022-03-25 14:27:08 +08:00
@vcfghtyjc 编译器不能 cache 一下每个文件的处理结果,遇到相同的文件直接用之前处理好的结果吗?
----

这就是 C++20 module ,但是前提是要承认“一个头文件里面的行为不受它之前 include 的其他头文件影响”。

现在的编译流程不具有这个约定。你八竿子搭不上边的 #define 可以影响到后面 include 的头文件。
ipwx
2022-03-25 14:29:45 +08:00
@vcfghtyjc 那能不能编译前自动给每个 data structure 都加个判断,除非声明就是要重复引用?
----

编译器其实没那么弱鸡,C 语言的头文件 include 也挺快。C++ 之所以那么慢还是因为生命定义全部放进头文件了,而且全都是超级复杂的模板。meta-programming 你学习一下就知道多强大了,这玩意儿可是编译期就图灵完全的。

相当于你能在代码里写递归、写斐波那契数列、写 whatever 东西,让编译期在编译的时候把结果算出来,而不是在运行的时候算出来。当然,前提是你写得出来。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/842838

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX