C++代码规范 头文件

2024-09-04 06:44
文章标签 代码 c++ 头文件 规范

本文主要是介绍C++代码规范 头文件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

这篇关于C++代码规范 头文件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1135294

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能