C++代码规范 头文件

news2024/9/23 5:17:34

1. 头文件

通常每个 .cc 文件应该有一个配套的 .h 文件. 常见的例外情况包括单元测试和仅有 main() 函数的 .cc 文件.

正确使用头文件会大大改善代码的可读性和执行文件的大小、性能.

下面的规则将带你规避头文件的各种误区.

1.1. 自给自足的头文件

Tip

头文件应该自给自足 (self-contained, 也就是可以独立编译), 并以 .h 为扩展名. 给需要被导入 (include) 但不属于头文件的文件设置为 .inc 扩展名, 并尽量避免使用.

所有头文件应该自给自足, 也就是头文件的使用者和重构工具在导入文件时无需任何特殊的前提条件. 具体来说, 头文件要有头文件防护符 (header guards, 1.2. #define 防护符),并导入它所需的所有其它头文件.

若头文件声明了内联函数 (inline function) 或模版 (template), 而且头文件的使用者需要实例化 (instantiate) 这些组件时, 头文件必须直接或通过导入的文件间接提供这些组件的实现 (definition). 不要把这些实现放到另一个头文件里 (例如 -inl.h 文件) 再导入进来; 这是过去的惯例, 但现在被禁止了. 若模版的所有实例化过程都仅出现在一个 .cc 文件中, 比如采用显式 (explicit) 实例化, 或者只有这个 .cc 文件会用到模版定义 (definition), 那么可以把模版的定义放在这个文件里.

在少数情况下, 用于导入的文件不能自给自足. 它们通常是要在特殊的地方导入, 例如另一个文件中间的某处. 此类文件不需要使用头文件防护符, 也不需要导入它的依赖 (prerequisite). 此类文件应该使用 .inc 扩展名. 尽量少用这种文件, 可行时应该采用自给自足的头文件.

1.2. #define 防护符

Tip

所有头文件都应该用 #define 防护符来防止重复导入. 防护符的格式是: <项目>_<路径>_<文件名>_H_ .

为了保证符号的唯一性, 防护符的名称应该基于该文件在项目目录中的完整文件路径. 例如, foo 项目中的文件 foo/src/bar/baz.h 应该有如下防护:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif  // FOO_BAR_BAZ_H_

1.3. 导入你的依赖

Tip

若代码文件或头文件引用了其他地方定义的符号 (symbol), 该文件应该直接导入 (include) 提供该符号的声明 (declaration) 或者定义 (definition) 的头文件. 不应该为了其他原因而导入头文件.

不要依赖间接导入. 这样, 人们删除不再需要的 #include 语句时, 才不会影响使用者. 此规则也适用于配套的文件: 若 foo.cc 使用了 bar.h 的符号, 就需要导入 bar.h, 即使 foo.h 已经导入了 bar.h.

1.4. 前向声明

Tip

尽量避免使用前向声明. 应该 导入你所需的头文件。

定义:

前向声明 (forward declaration) 是没有对应定义 (definition) 的声明.

// 在 C++ 源码文件中:
class B;
void FuncInB();
extern int variable_in_b;
ABSL_DECLARE_FLAG(flag_in_b);

优点:

  • 使用前向声明能节约编译时间, 因为 #include 会迫使编译器打开更多的文件并处理更多的输入.

  • 使用前向声明能避免不必要的重复编译. 若使用 #include, 头文件中无关的改动也会触发重新编译.

缺点:

  • 前向声明隐藏了依赖关系, 可能会让人忽略头文件变化后必要的重新编译过程.

  • 相比 #include, 前向声明的存在会让自动化工具难以发现定义该符号的模块.

  • 修改库 (library) 时可能破坏前向声明. 函数或模板的前向声明会阻碍头文件的负责人修改 API, 例如拓宽 (widening) 参数类型, 为模版参数添加默认值, 迁移到新的命名空间等等, 而这些操作本是无碍的.

  • 为 std:: 命名空间的符号提供前向声明会产生未定义行为 (undefined behavior).

  • 很难判断什么时候该用前向声明, 什么时候该用 #include . 用前向声明代替 #include 时, 可能会悄然改变代码的含义:

    // b.h:
    struct B {};
    struct D : B {};
    
    // good_user.cc:
    #include "b.h"
    void f(B*);
    void f(void*);
    void test(D* x) { f(x); }  // 调用 f(B*)
    

若用 B 和 D 的前向声明替代 #includetest() 会调用 f(void*) .

  • 为多个符号添加前向声明比直接 #include 更冗长.

  • 为兼容前向声明而设计的代码 (比如用指针成员代替对象成员) 更慢更复杂.

结论:

尽量避免为其他项目定义的实体提供前向声明.

1.5. 内联函数¶

Tip

只把 10 行以下的小函数定义为内联 (inline).

定义:

你可以通过声明建议编译器展开内联函数, 而不是使用正常的函数调用机制.

优点:

只要内联函数体积较小, 内联函数可以令目标代码 (object code) 更加高效. 我们鼓励对存取函数 (accessors)、变异函数 (mutators) 和其它短小且影响性能的函数使用内联展开.

缺点:

滥用内联将拖慢程序. 根据函数体积, 内联可能会增加或减少代码体积. 通常, 内联展开非常短小的存取函数会减少代码大小, 但内联一个巨大的函数将显著增加代码大小. 在现代处理器上, 通常代码越小执行越快, 因为指令缓存利用率高.

结论:

合理的经验法则是不要内联超过 10 行的函数. 谨慎对待析构函数. 析构函数往往比表面上更长, 因为会暗中调用成员和基类的析构函数!

另一个实用的经验准则: 内联那些有循环或 switch 语句的函数通常得不偿失 (除非这些循环或 switch 语句通常不执行).

应该注意, 即使函数被声明为内联函数, 也不一定真的会被内联; 比如, 虚函数和递归函数通常不会被内联. 一般不应该声明递归函数为内联. (YuleFox 注: 递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 为虚函数声明内联的主要目的是在类 (class) 中定义该函数, 以便于使用该函数或注释其行为. 这常用于存取函数和变异函数.

1.6. #include 的路径及顺序

Tip

推荐按照以下顺序导入头文件: 配套的头文件, C 语言系统库头文件, C++ 标准库头文件, 其他库的头文件, 本项目的头文件.

头文件的路径应相对于项目源码目录, 不能出现 UNIX 目录别名 (alias) . (当前目录) 或 .. (上级目录). 例如, 应该按如下方式导入 google-awesome-project/src/base/logging.h:

#include "base/logging.h"

在 dir/foo.cc 或 dir/foo_test.cc 这两个实现或测试 dir2/foo2.h 内容的文件中, 按如下顺序导入头文件:

  1. dir2/foo2.h.

  2. 空行

  3. C 语言系统文件 (确切地说: 用使用方括号和 .h 扩展名的头文件), 例如 <unistd.h> 和 <stdlib.h>.

  4. 空行

  5. C++ 标准库头文件 (不含扩展名), 例如 <algorithm> 和 <cstddef>.

  6. 空行

  7. 其他库的 .h 文件.

  8. 空行

  9. 本项目的 .h 文件.

每个非空的分组之间用空行隔开.

这种顺序可以确保在 dir2/foo2.h 缺少必要的导入时, 构建 (build) dir/foo.cc 或 dir/foo_test.cc 会失败. 这样维护这些文件的人会首先发现构建失败, 而不是维护其他库的无辜的人.

dir/foo.cc 和 dir2/foo2.h 通常位于同一目录下 (如 base/basictypes_unittest.cc 和 base/basictypes.h), 但有时也放在不同目录下.

注意 C 语言头文件 (如 stddef.h) 和对应的 C++ 头文件 (cstddef) 是等效的. 两种风格都可以接受, 但是最好和现有代码保持一致.

每个分组内部的导入语句应该按字母序排列. 注意旧代码可能没有遵守这条规则, 应该在方便时进行修正.

举例来说, google-awesome-project/src/foo/internal/fooserver.cc 的导入语句如下:

#include "foo/server/fooserver.h"

#include <sys/types.h>
#include <unistd.h>

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "foo/server/bar.h"
#include "third_party/absl/flags/flag.h"

例外:

有时平台相关的 (system-specific) 代码需要有条件地导入 (conditional include),此时可以在其他导入语句后放置条件导入语句. 当然, 尽量保持平台相关的代码简洁且影响范围小. 例如:

#include "foo/public/fooserver.h"

#include "base/port.h"  // 为了 LANG_CXX11.

#ifdef LANG_CXX11
#include <initializer_list>
#endif  // LANG_CXX11

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2102528.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[某度信息流]SQL164,2021年11月每天新用户的次日留存率

牛客网在线编程 思路&#xff1a; 首先找出用户的注册日期&#xff0c;即date(min(in_time)) 转成date形式 建立两个辅助表&#xff0c;我先放代码&#xff0c;然后进行解释 withuser_reg as (selectuid,date(min(in_time)) as first_datefromtb_user_loggroup by1),…

【路径规划】在二维环境中快速探索随机树和路径规划的示例

摘要 本文介绍了快速探索随机树&#xff08;Rapidly-exploring Random Tree, RRT&#xff09;算法在二维环境中的路径规划应用。RRT是一种随机采样算法&#xff0c;能够快速构建从起点到目标点的路径&#xff0c;特别适用于复杂环境中的机器人路径规划。通过在随机方向上扩展树…

Vue3实时更新时间(年-月-日 时:分:秒)

代码案例 <script lang"ts" setup> import { ref,onMounted } from vue; const timer ref() const date ref("")//年月日 const moreTime ref("")//时分秒 onMounted(()>{//创建定时器1秒执行一次timer.value setInterval(() >…

数学建模强化宝典(10)多元线性回归模型

一、介绍 多元线性回归模型&#xff08;Multiple Linear Regression Model&#xff09;是一种用于分析多个自变量&#xff08;解释变量、预测变量&#xff09;与单个因变量&#xff08;响应变量、被预测变量&#xff09;之间线性关系的统计模型。这种模型假设因变量的变化可以通…

Proxyless的多活流量和微服务治理

1. 引言 1.1 项目的背景及意义 在当今的微服务架构中&#xff0c;应用程序通常被拆分成多个独立的服务&#xff0c;这些服务通过网络进行通信。这种架构的优势在于可以提高系统的可扩展性和灵活性&#xff0c;但也带来了新的挑战&#xff0c;比如&#xff1a; 服务间通信的复…

Excel 将行和列转置的两种方法

方法一&#xff1a; 方法二&#xff1a;使用transpose公式

构造+模拟,CF 873D - Merge Sort

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 873D - Merge Sort 二、解题报告 1、思路分析 考虑初始会调用一次&#…

Opencv中的直方图(3)直方图比较函数compareHist()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 比较两个直方图。 函数 cv::compareHist 使用指定的方法比较两个密集或两个稀疏直方图。 该函数返回 d ( H 1 , H 2 ) d(H_1, H_2) d(H1​,H2​…

虚幻引擎VR游戏开发01 | VR设备和术语

四款Unreal Engine默认配套按键映射的VR设备 IMC按键映射 Oculus Touch (R) Grip Axis: 代表Oculus Rift或Quest设备的右手控制器的抓握轴输入。Valve Index (R) Grip Axis: 代表Valve Index设备的右手控制器的抓握轴输入。Vive (R) Grip: 代表HTC Vive设备的右手控制器的抓握…

[物理专题]经典浮力题目的Fh图像绘制

这段代码用于绘制物体在液体中受到的浮力变化的图像&#xff0c;它有多个好处&#xff1a; 直观展示数据&#xff1a;通过图形化展示&#xff0c;可以直观地看到物体在液体中浸入深度与受到的浮力之间的关系。 教育和学习工具&#xff1a;这种类型的图像常用于教育目的&#x…

不小心删除了 Android 手机上的短信?3 步流程恢复误删除的短信以及图片、视频、联系人

不小心删除了 Android 手机上的短信&#xff1f;别担心&#xff0c;Android 版奇客数据恢复工具可以帮助您通过简单的 3 步流程恢复已删除的短信以及图片、视频、联系人等。 如何在 Android 上恢复已删除的短信 不小心删除了 Android 手机上的短信&#xff1f;Android 版奇客数…

u盘格式化数据还能恢复吗?点击了解实用教程

U盘是电子数据存储设备&#xff0c;我们主要用它来转移数据、随身携带数据等。同时U盘在使用过程中常会遇到问题&#xff0c;比如U盘中毒&#xff0c;U盘中毒会导致里面保存的数据文件无法读取&#xff0c;我们需要进行U盘格式化。格式化之后的U盘才可以继续使用&#xff0c;那…

611.有效三角形的个数

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;双指针&#xff09; 如何构成一个三角形呢&#xff1f; 只需要两边之和大于第三边&#xff1b; 但是&#xff0c;如果已知三条边的大小关系&#xff0c;只需要两条较小边的和大于第三条边即可。 所以&#xff0c;我…

云计算实训41——部署project_exam_system项目(续)

# 创建脚本&#xff0c;可以在java环境中运行任何的jar包或者war包#!/bin/bash/usr/local/jdk/bin/java -jar /java/src/*.?ar一、思路分析 &#xff08;1&#xff09;nginx 1、下载镜像&#xff0c;将本地的dist项目的目录挂载在容器的/usr/share/nginx/html/ 2、启动容器 …

哈希:哈希函数 | 哈希概念 | 哈希冲突 | 闭散列 | 开散列

&#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系列 &#x1f308;Linux学习专栏&#xff1a; 南桥谈Linux &#x1f308;数据结构学习专栏&#xff1a; 数据结构杂谈 &#x1f308;数据…

在线式环氧乙烷检测仪:现代工业生产中的环氧乙烷安全监测

在现代工业生产的广阔领域中&#xff0c;环氧乙烷&#xff08;C2H4O&#xff09;作为一种不可或缺的化工原料&#xff0c;其应用范围广泛且深远&#xff0c;涵盖了涂料、树脂、塑料、印染、纺织品等多个关键行业。然而&#xff0c;环氧乙烷的化学性质极为活泼&#xff0c;不仅易…

小阿轩yx-云原生存储Rook部署Ceph

小阿轩yx-云原生存储Rook部署Ceph 前言 Rook 一款云原生存储编排服务工具由云原生计算基金会&#xff08;CNCF&#xff09;孵化&#xff0c;且于2020年10月正式进入毕业阶段。并不直接提供数据存储方案&#xff0c;而是集成了各种存储解决方案&#xff0c;并通过一种自管理、…

SprinBoot+Vue图书馆预约与占座微信小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue3.6 uniapp代码 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平…

“Flash闪存”介绍 及 “SD NAND Flash”产品的测试含例程

文章目录 前言一、“FLASH闪存”是什么&#xff1f;1. 简介2. 特点3. 未来发展 二、SD NAND Flash1. 概述2. 特点3. 引脚分配4. 数据传输模式5. SD NAND寄存器6. 通电图7. 参考设计 三、STM32测试例程1. 初始化2. 但数据块测试3. 多数据块测试4. 状态缓冲 前言 本篇除了对flas…

Java JVM 垃圾回收算法详解

Java 虚拟机&#xff08;JVM&#xff09;是运行 Java 应用程序的核心&#xff0c;它的垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制是 JVM 中非常重要的一个部分。垃圾回收的主要任务是自动管理内存&#xff0c;回收那些不再被使用的对象&#xff0c;从而释放内…