当前位置: 首页 > news >正文

precompilation-headers 以及在cmake中的实现

目录

    • 1.gcc/g++ 预编译头文件
      • 1.1.头文件
      • 1.2.main.cpp
      • 1.3.目录结构
      • 1.4.编译
      • 1.5.测试
    • 2.cmake 中使用预编译头文件
    • 3.cmake中预编译头文件不能使用install()命令导出
      • 3.1.核心概念解读
      • 3.2.为什么需要谨慎对待?
      • 3.3.正确的实践方法
      • 3.4.总结
    • 4.头文件的处理
      • 4.1.两种包含语法的含义
      • 4.2.示例
    • 参考资料

1.gcc/g++ 预编译头文件

1.1.头文件

为了测试,把常用c++标准头文件都放在一起.

// my.h
#ifndef MY_H
#define MY_H// 添加你最常用的稳定头文件
#include <algorithm>
#include <iomanip>
#include <list>
#include <ostream>
#include <streambuf>
#include <bitset>
#include <ios>
#include <locale>
#include <queue>
#include <string>
#include <complex>
#include <iosfwd>
#include <map>
#include <set>
#include <typeinfo>
#include <deque>
#include <iostream>
#include <memory>
#include <sstream>
#include <utility>
#include <exception>
#include <istream>
#include <new>
#include <stack>
#include <valarray>
#include <fstream>
#include <iterator>
#include <numeric>
#include <stdexcept>
#include <vector>
#include <array> 
#include <condition_variable> 
#include <mutex> 
#include <scoped_allocator> 
#include <type_traits>
#include <atomic> 
#include <forward_list> 
#include <random> 
#include <system_error> 
#include <typeindex>
#include <chrono> 
#include <future> 
#include <ratio> 
#include <thread> 
#include <unordered_map>
#include <codecvt> 
#include <initializer_list> 
#include <regex> 
#include <tuple> 
#include <unordered_set>
#include <shared_mutex> 
#include <any> 
#include <execution> 
#include <memory_resource> 
#include <string_view> 
#include <variant>
#include <charconv> 
#include <filesystem> 
#include <optional>
#include <cassert> 
#include <clocale> 
#include <cstdarg> 
#include <cstring>
#include <cctype> 
#include <cmath> 
#include <cstddef> 
#include <ctime>
#include <cerrno> 
#include <csetjmp> 
#include <cstdio> 
#include <cwchar>
#include <cfloat> 
#include <csignal> 
#include <cstdlib> 
#include <cwctype>
#include <climits> 
#include <cfenv> 
#include <cinttypes> 
#include <cstdint> 
#include <cuchar>// 如果需要,也可以包含一些稳定的自定义头文件
// #include "my_stable_lib.h"#endif // MY_H

1.2.main.cpp

#include "my.h"int main(void)
{std::cout<<"Hello World!"<<std::endl;return 0;
}

1.3.目录结构

├── include
│   └── my.h
└── main.cpp

1.4.编译

# 编译 预编译头文件 my.h.gch
cd include/
g++ -x c++-header my.h -o my.h.gch

1.5.测试

两种方式使用预编译头文件:

  • 自动使用预编译头文件
time g++ -I./include main.cpp -o main
# 可以看到运行时间是0.47s
real    0m0.470s
user    0m0.226s
sys     0m0.037s
  • 显式使用预编译头文件
time g++ -I./include -include my.h main.cpp -o main
# 可以看到也是起作用的
real    0m0.457s
user    0m0.240s
sys     0m0.042s

注意:显式指定预编译头文件,在代码中可以不包含头文件,例如main.cpp代码如下:

int main(void)
{std::cout<<"Hello World!"<<std::endl;return 0;
}

对比不使用预编译头文件:

time g++ -I./include -include my.h main.cpp -o main 
# 需要1.795s
real    0m1.795s
user    0m1.031s
sys     0m0.176s

2.cmake 中使用预编译头文件

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)set(CMAKE_EXPORT_COMPILE_COMMANDS ON)project(precompiled_headers_test LANGUAGES CXX)
add_executable(main main.cpp)
# 使用预编译头文件
target_precompile_headers(main PRIVATE ${CMAKE_SOURCE_DIR}/include/my.h)
target_include_directories(main PRIVATE ${CMAKE_SOURCE_DIR}/include)

include/my.h被用于预编译头文件.

cmake -S . -B build
time cmake --build build
# 修改main.cpp对比编译时间

3.cmake中预编译头文件不能使用install()命令导出

在cmake中使用precompilation headers有如下疑问:

however, this shouldn’t be done for targets exported with the install() command.
Other porjects shouldn’t be forced to comsume our precompiled headers(as it’s unconventional).

为什么不能用于install()命令?这段英文的具体含义是什么?

这个问题,确实是使用 CMake 预编译头文件时一个非常重要且精妙的设计考量.
简单来说,其核心思想是:保持接口的干净,将实现细节隐藏起来,避免给使用你项目的人带来意外的麻烦.

下面我为你详细解释一下这段英文的含义和背后的原因.

3.1.核心概念解读

首先,我们来理解两个关键点:

  1. install() 命令与项目发布: CMake 的install()命令用于定义如何将你的库(包括头文件,库文件等)安装到系统中,
    以便其他项目能够通过 find_package() 来找到并使用它.这相当于你制作了一个“产品”发布给他人.
  2. 预编译头文件(PCH)的性质: PCH 是一个实现细节和构建优化手段.它和编译器,编译选项等强相关,目的是为了加快你自身项目的编译速度.

3.2.为什么需要谨慎对待?

将这两点结合起来,问题就清晰了:

  • PCH 不是接口契约:你的库对外提供的接口(API)是其头文件中的函数,类声明等.
    而 PCH 是为了生成这些接口的最终二进制文件过程中的一个加速工具.强制下游项目(即使用你的库的项目)也使用你的 PCH,
    就像强迫别人必须用和你一模一样的工具链来组装他们自己的产品一样,是不合理且具有侵入性的.
  • 兼容性问题:不同项目的编译环境(编译器版本,编译标志,预定义宏等)可能截然不同.
    你的 PCH 是在你的特定构建环境下生成的,直接强加给另一个项目,极有可能因为环境不兼容而导致编译失败, 或产生难以排查的警告(例如 GCC 会报 -Winvalid-pch 警告).
  • 控制权反转:构建时应使用哪些 PCH,这属于构建策略,理应由每个项目自己决定.
    如果你的库通过导出目标将 PCH 设置为 PUBLIC 或 INTERFACE 属性,就相当于剥夺了下游项目的这个控制权.

3.3.正确的实践方法

那么,应该如何正确地使用 target_precompile_headers 呢?CMake 官方文档和建议如下:

  • 优先使用 PRIVATE 作用域:
    对于大多数不打算被其他项目使用的可执行文件或静态库,应使用 PRIVATE 关键字.这确保 PCH 只用于当前目标本身.
# 正确:PCH 仅用于 MyApp 自身的构建,不会传播出去
target_precompile_headers(MyApp PRIVATE <vector> <string> "my_stable_headers.h")
  • 导出目标时的保护措施:
    如果你的目标会被 install(EXPORT) 导出供他人使用,但又希望在其内部使用 PCH 来加速编译,那么需要采取隔离措施.
    使用生成器表达式进行隔离:
    如果确有需要(例如,你创建一个专门用于提供统一 PCH 的接口库),一个更稳妥的做法是使用 $<BUILD_INTERFACE:...> 生成器表达式.
    这可以确保 PCH 设置只在当前构建树(build tree)内有效,而不会包含在安装导出的目标文件里.
# 创建一个专门管理 PCH 的接口库(谨慎使用)
add_library(my_project_pch INTERFACE)
# 使用 BUILD_INTERFACE 确保只在构建时生效,安装时被排除
target_precompile_headers(my_project_pch INTERFACE$<BUILD_INTERFACE:<string>>$<BUILD_INTERFACE:<vector>>
)
# 其他目标在构建时链接这个库以获取 PCH
target_link_libraries(MyApp PRIVATE my_project_pch)

3.4.总结

总而言之,这段英文提示的本质是软件工程中的一条最佳实践:明确定义接口,并最小化接口的约束.
预编译头文件是一个强大的构建期优化工具,但它应该被当作内部实现细节来处理.
当发布你的项目以供他人使用时,应当提供一个干净,无附加条件的接口,让使用者拥有对其自身构建过程的完全控制权.

4.头文件的处理

有如下内容让人费解:

  • header.h is interpreted as relative to the current source directly and will be included with an absolute path.
  • [["header.h"]] is interpreted according to the compiler’s implementation and is usually found in the
    INCLUDE_DIRECTORIES variable. Use target_include_directories() to configure it.

你在CMake书籍中读到的这段内容,精确地指出了头文件包含方式与CMake路径配置的核心关系.这确实是理解现代CMake依赖管理的关键一环.

4.1.两种包含语法的含义

书中的描述区分了两种头文件包含方式在CMake视角下的不同含义:

  • ​header.h(无引号或尖括号)​​:这种写法在CMake中被明确地解释为相对于当前源文件目录(即CMAKE_CURRENT_SOURCE_DIR)的路径.
    CMake在处理时,会倾向于将其转换为一个绝对路径.这意味着,无论你的包含路径(INCLUDE_DIRECTORIES)如何设置,编译器最终接收到的很可能是一个完整的绝对路径.
  • ​"header.h"<header.h>​: 这种带引号或尖括号的写法,是C/C++标准中的正式语法.CMake不会像上一种情况那样对其进行“特殊处理”或强制转换.
    ​编译器会根据其自身的搜索规则来查找这个文件,而搜索的范围,正依赖于通过 target_include_directories()命令设置的 INCLUDE_DIRECTORIES变量.

4.2.示例

MyProject/
├── CMakeLists.txt
├── include/
│   └── mylib.h
├── src/
│   ├── mylib.cpp
│   └── main.cpp
cmake_minimum_required(VERSION 3.10)
project(MyProject)# 创建库目标
add_library(mylib src/mylib.cpp)
# 公共头文件目录对mylib自身和链接它的目标均可见
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)# 创建可执行文件目标
add_executable(myexe src/main.cpp)
# 可执行文件链接库,会自动获得mylib的PUBLIC头文件路径
target_link_libraries(myexe PRIVATE mylib)

这样,当编译 myexe时,编译器就能在 ${CMAKE_CURRENT_SOURCE_DIR}/include目录下找到 #include "mylib.h"这个头文件了.

参考资料

  • 3.22 使用预编译头
  • Using Precompiled Headers
http://www.dtcms.com/a/519956.html

相关文章:

  • php做的网站用什么后台ui设计是怎么实现的
  • 怎么建设宣传网站网页制作公司兼职
  • llama.cpp批处理选择不同模型启动
  • 《从零构建企业级 Java+DeepSeek 智能应用:SpringBoot/Vert.x 双引擎实战,打造热榜级 AI 开发指南》
  • 【存储概念】存储系统中块设备、分区、文件系统的概念及关系
  • (第二篇)Spring AI 基础入门:从环境搭建到模型接入全攻略(覆盖国内外模型 + 本地部署)
  • 容器适配器:Stack与Queue的底层奥秘
  • 2025年10月23日Github流行趋势
  • 上海外贸网站建设公司价格做兼职设计去哪个网站
  • 免费效果图网站wordpress分类目录导航
  • 【完整源码+数据集+部署教程】【运动的&足球】足球比赛分析系统源码&数据集全套:改进yolo11-RFAConv
  • YARN简介
  • PSO-Transformer-BiLSTM分类预测/故障诊断,优化参数为注意力机制头数、学习率、正则化系数、隐藏层单元,图很多,包括分类效果图,混淆矩阵图
  • AJAX 知识
  • 做淘宝推广开网站合适全球最大的设计网站
  • Java-157 MongoDB 存储引擎 WiredTiger vs InMemory:何时用、怎么配、如何验证 mongod.conf
  • 详细-vue3项目初始化配置流程
  • 电子科技网站太原seo排名
  • 销售记账-成本中心/成本会计分配
  • TensorFlow深度学习实战——链路预测
  • 广州网站建设公司品牌太和县建设局网站
  • 帝国网站的互动专栏怎么做做ppt兼职网站
  • SpringBoot-数据访问之JDBC
  • Linux操作系统-父进程的等待:一个关于回收与终结的故事
  • Adobe After Effects 2025(AE2025解锁版) 电影级特效
  • 云栖实录 | DataWorks 发布下一代 Data+AI 一体化平台,开启企业智能数据新时代
  • uv add openai 和 uv pip install openai 的区别
  • 安装了conda和uv如何创建一个项目?
  • 策略模式解决的核心问题是什么?
  • Jenkins远程命令执行漏洞复现:原理详解+环境搭建+渗透实践(CVE-2018-1000861 3种方法)