C++ 头文件:预处理、内联函数、模板函数

预处理

  • 编译之前(预处理阶段)执行

  • 预处理功能举例

    • #include
    • 头文件保护符
      • 避免同一个文件被include多次
        • 通过判断预处理变量是否已定义:#ifndef、#define、#endif
      • 预处理变量名全局唯一(通常通过头文件中的类名来定义保护符的名字,个人感觉 namespace name + class name 来定义较好)
      • 缺点
        • 如果项目的不同模块中有同名的两个头文件(内容可能不同),其头文件保护符很可能也是相同的,这时可能会造成其中一个头文件被意外exclude
        • #ifndef/#endif需要成对出现
    • #pragma once
      • 同样也是避免同一个文件被include多次
        • 此处“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件
        • 无法对一个头文件中的一段代码作pragma once声明,而只能针对文件
      • 缺点
        • non-standard
        • 如果项目中某个头文件被拷贝多次,编译时会认为这是多个不同头文件(同时也是优点,如项目中不同模块的两个同名但不同内容头文件,不使用头文件保护符,而是使用这种方式就不会意外exclude其中一个)
  • 预处理变量及注意事项

    • #define 指令把一个名字设定为预处理变量
    • #ifdef、#ifndef 检测预处理变量已定义、未定义
    • 预处理变量无视C++语言中关于作用域的规则(即不能提供任何封装性)
    • 整个程序中的 预处理变量(包括头文件保护符)必须唯一

概念及规则

  • 编译单元:即一个源文件+其include的文件,编译后可生成一个目标文件
  • 可声明多次,但只能定义一次
  • 可重声明函数,但不能重声明结构/类
  • 分离式编译过程中,预处理器在每个编译单元中查找头文件保护符是否被定义

内联函数

模板函数

通常模板函数声明和定义都位于头文件,原因为在同一编译单元内,编译器要根据模板函数定义来实例化模板函数,而实例化模板函数发生的的位置在源文件,因此模板函数的定义应位于头文件

另外当多个源文件中都有相同的模板函数实例化时,每个编译单元内确实都生成了对应的实例化函数,但它是一个弱符号,最终在链接阶段只会选择其中一个,从而不会出现多重定义问题,具体可参考:链接器如何解析多重定义的全局符号

重复定义相关测试

类成员

  • 成员函数定义在头文件中类外部时,可能会有多重定义的错误(编译或者链接阶段都有可能)
  • 成员函数定义在头文件中类内部时为内联函数,不会出现多重定义错误
  • 类静态成员的定义在头文件中类外部时,可能会有多重定义的错误
  • 当不对类静态成员进行定义时,代码中如果有对其访问则会报未定义编译错误

全局变量

  • 静态全局变量、常量全局变量为内部链接 ,每个编译单元中可保存一份副本,在头文件中的定义不会引起重复定义错误
  • 普通全局变量如果定义在头文件,可能引起重复定义错误

参考

《C++ Primer 第五版》
《Effective C++ 第三版》
Is #pragma once a safe include guard?
#ifndef 与 #program once 的区别
头文件与cpp文件为什么要分开写
头文件重复包含和变量重复定义
How is compilation unit defined in c++?
What is a “translation unit” in C++
How does the linker handle identical template instantiations across translation units?
separate-template-class-defn-from-decl

原文地址:https://www.cnblogs.com/wangzhiyi/p/12562285.html