15.0 Note
(1) `带 初始化值` 的 `(extern) 声明` 被视为 `定义: extern 被忽略`
(2) 默认初始化
global/namespace scope 的 变量定义 才会...
15.1 分离编译
1 按 逻辑结构(模块) 在 物理结构(文件) 上划分
(1) 目标: `接口与实现分离` -> 减少依赖 & 节省 编译时间
(2) 源文件
|
|/
预处理
|
| [1] 宏
| [2] #include 包含头文件
|/
编译单元: compiler 处理
15.2 链接
1 链接方式: linker 能 access name 的 scope: 外部/内部/无(linker 看不到) 编译单元
[1] 外部链接
函数 / global 对象
[2] 内部链接
1] static 实体 (对象/函数): 必为 内部链接
2] namespace (类) 中 const 对象 / constexpr 对象 / 类型别名
默认 内部链接, `extern 可改链接方式为 外部链接`
[3] 无链接
local non-static 变量名
2 头文件: 应该包含什么实体(一般原则) & 不能包含什么(否则, 编译报错)?
#include 机制: #include 源文件 & #inclue 头文件(实现 ODR)
(1) 应该包含什么
想在 多个编译单元
中 可见(可被 access)
的 ...: 需保持 ODR(单一定义规则: 定义一致性)
而非 static 实体 (对象/函数)
1) 5组 定义
类型 struct Point { int x, y; };
enum class Light { red, yellow, green };
模板 template <typename T> class C { /* */ };
inline 函数
constexpr 函数
constexpr 对象 constexpr float pi2 = 3.1415926;
const 对象 const float pi = 3.14;
具名 namespace (定义) & inline namespace
2) 4组 声明
名字 class Matrix;
模板 template <typename T> class Z;
函数 extern int strlen(const char*);
数据 extern int a;
3) 4种 预处理
宏 定义 #define
包含 指令 #include
条件编译 指令 #ifdef __cplusplus
注释
4)
类型别名 using value_type = long;
编译时断言 static_asser(4 <= sizeof(int), "small ints");
note: `条件编译` 不是 编译时 机制, 是 `预处理 / 预编译` 时 机制
Note
——————————————————————————————————————————————————————————————————
inline 函数 外部编译单元 是否可调用
——————————————————————————————————————————————————————————————————
[1] `头文件` 中 `定义` + #include 头文件 是
——————————————————————————————————————————————————————————————————
[2] `本 编译单元 内 定义` inline 函数 否
——————————————————————————————————————————————————————————————————
(2) 不能包含什么
[1] 普通 `函数定义
[2] 普通 `数据定义`
[3] 匿名 namespace
[4] `集合` 定义 int tbl[] = {1, 2};
3 ODR/单一定义规则
(1) `模板 & inline 函数: 2个定义 可视为 唯一定义, 只要 `不违反 ODR`
(2) 违反 ODR
[1] `重定义`
[2] 2个 编译单元内容同, 但 `含义不同`: 被 `类型别名/宏 改变了含义`
防止违反 ODR: 令 `头文件` 尽可能地 `自包含`
// === [2]
// file1.cpp
typedef int X;
struct S {X x; char b; };
// file2.cpp
typedef char X;
struct S {X x; char b; };
// === 防止违反ODR
// 1) s.h
struct S {Point a; char b; };
// 2) file1.cpp
#define Point int
#include "s.h"
// 2) file2.cpp
class Point { /* ... */ };
#include "s.h"
| 令 `头文件` 尽可能地 `自包含`:
| 将 s.h 中 struct S 中 Point 定义 放 s.h 前
| 错误就能检测出来
|/
// 1) s.h
class Point { /* ... */ };
struct S {Point a; char b; };
// 2) file1.cpp
// ...
// 3) file2.cpp
#include "s.h"
4 C/C++ 标准头文件 关系
C++ <cX>
|
| 一一对应
|
C <X.h>
(1) <cstdio>
#ifdef __cplusplus // 1) 区分 `C++ 代码` 和 `用于 C 编译器 的代码`
namespace std
{
extern "C" // 2) `链接` 问题
{
#endif
int printf(const char*, ...);
// ...
#ifdef __cplusplus
}
}
// ...
using std::printf; // 3) `namespace` 问题
[1] 令 printf 在 `全局 namespace` 中 可用
有的 compiler 的 <cstdio> 实现中没有用 `using 声明` 声明 printf, 则
C++ user 程序 #include <cstdio> 后, 要用 std::printf("hello");
而不是 printf("hello"); // error: 无全局 printf
[2] std::printf, 仍是那个 C printf()
(2) <stdio.h>
#include <cstdio>
using std::printf; // C user 程序: 加上肯定不会错,
// 没加 & <cstdio> 实现中没用 `using 声明` 声明 printf => 直接用 printf 会 compile error
5 链接 C 代码 到 C++ 程序
(1) extern "C": C 表示 `链接规范`
告诉 C++ compiler `按 指定语言 (C) 方式 (name, 调用习惯 等) 处理` 其中 content, 以使 `linker 按预期 link`
(2) `C 和 C++ common 头文件: extern "C" + 条件编译`
#ifdef __cplusplus
extern "C" {
#endif
char* strcpy(char*, const char*);
#ifdef __cplusplus
}
#endif
6 链接 和 函数指针: C & C++ 混合编译
(1) 函数指针声明 & 函数原型声明 的 链接规范 要相相同 -> 再 use mathched 函数调用
_ _ _ _
| extern "C" int compareC(const void*, const void*); // 1) 函数原型声明
|
| - - int compare(const void*, const void*);
| |
| |
| | typedef int (*FT)(const void*, const void*);
| | void sort(void* p, size_t sz, FT cmp);
| | |\
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|
| extern "C" {
| typedef int (*CFT)(const void*, const void*); // 2) 函数指针声明
| void sortC(void* p, size_t sz, CFT cmp);
| |\
|__ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| }
|
| void f(char* v, int sz)
| {
| sort(v, sz, &compare); // C++ 函数指针
|
|- - - - -> sortC(v, sz, &compareC); // C 函数指针 // 3) 函数调用
}
(2) std::function / 带捕获 的 lambda: 无法跨越 语言的障碍
7 全局变量
1) 问题: 难维护 & 多线程中数据竞争
2) 使用应限制在 `单一源文件`
[1] static (C in practice)
[2] 放 无名 namespace: 效果 像 `内部链接`
15.3 头文件 使用
1 单头文件 组织
(1) extern
可确保 多个 .cpp #include 1个 .h 时, 不会发生 重定义
(2) compiler & linker 能检测出的错误: 1] 类型不匹配 2] undeclared & 1] undefined 2] redefined
(3) namespace
成员 声明与定义分离
: 显式限定 定义 新成员
, 还需 (在 namespace 中) add 新声明, 否则 compile error
namespace N
{
class C
{
pubic:
C f1();
};
}
N::C N::C::f1() { /* ... */ }
2 多头文件 组织
(1) 最小化 相互依赖: DIP/依赖倒置 => 插件式 软件架构: (C 语言实现) 多态
插件式: 无关性, 只要 新插件 handle.cpp 实现了 所要求的函数 f2, 就可以 替换掉 旧插件 handle.cpp
目标: `func1 调 func2` -> 2 种方法
1) `func1 直接依赖 func2`
func1&func2 均放 1.h
_ _ _ _ 依赖 / 控制流
| |
| |/
1.h: func1 + func2 + ...
|\
|
| 依赖
|
1.cpp
2) DIP & 插件式架构
依赖
_ _ _ _ _ _ _ _ _ _ _
| |
| |/ 抽象接口
1.h: func1 声明 handle.h: pHandle 声明
|\ |\
依赖 | | 依赖
| |
1.cpp handle.cpp:
func1 定义(高层) - - - - ->f2() 定义 (低层)
控制流 pHandle 的 函数指针成员 赋值 为 &f2
// === 1.h
#pragma once
#include "handle.h" // 依赖于 抽象
void f1();
// === 1.cpp
#include "1.h"
#include<iostream>
void f1()
{
std::cout << "f1():\n";
pHandle->f2();
}
// === handle.h
#pragma once
// === Hanlde: mem 存 函数指针
struct Handle
{
void (*f2)();
};
extern struct Handle* pHandle;
// === handle.cpp
// 插件式: 无关性, 只要 新插件 handle.cpp 实现了 所要求的函数 f2, 就可以 替换掉 旧插件 handle.cpp
#include "handle.h"
#include <iostream>
void f2()
{
std::cout << "f2():\n";
}
struct Handle handle = { &f2 };
struct Handle* pHandle = &handle;
// === main.cpp
#include "1.h"
int main()
{
f1();
}
(2) 逻辑模块 - 头文件 - 源文件
逻辑模块
|
| 一一对应: 定义 (逻辑模块) 特性
|/
头文件
|\ 一一被包含: 描述 (源文件) 接口 => `用户接口`
|
|
源文件
也 #include `other 头文件`: 视为 本源文件的 `实现者接口`
即, 用 `other 模块 实现 自身接口` 中 `所宣称的 服务`
(3) 多头文件 优势: 关注点局部化
[1] 易 find `依赖关系`
[2] 易 从 局部视角 "由内而外" 维护 & `伸缩` 到大规模( 万/.../亿 行 )程序`
[3] `编译速度 更快`: 可能 大幅提高
(4) 单/多 头文件组织
1) 避免多头文件 `过度使用`
逻辑划分尺度应合适, 若 太细, 如 每个结构声明 放 单独的头文件, 则头文件过多 很难管理
2) 单头文件 和 多头文件 `互补`, 不是互相替代的
3 包含保护
含 类定义/inline 函数
的 头文件
在 同一编译单元 被 #include 两次 -> compile error
解决:
(1) 去冗余
1] 减少 编译时间/编译依赖`
2] 很难用在 大规模程序: `希望 冗余`, 使各 part `独立可理解`
(2) 包含保护: 重复包含头文件 & 条件编译(第1次包含 时才 #include)
过度使用:声明/宏 大量引入 => `不可预知` 的 糟糕方式
#ifndef _FILE1_H
#define _FILE1_H
// ...
#endif
15.4 初始化和并发 & 程序终止
1 non-local (静态分配)变量
non-local (静态分配)变量 `初始化机制` 就是 `启动 C++ 程序的机制`
(1)初始化顺序
在 `单编译单元` 下 `与定义顺序一致`
在 多编译单元下 undefined => not 安全 => 初始化 `不要依赖 other 编译单元` 对象值
[1] `常量表达式` 初始化: `链接时初始化` -> 线程安全: 前提 `不能依赖 other 编译单元` 中 对象的值
[2] else : `运行时初始化`
(2)并发
: 多线程下 递增
等 带副作用的表达式
-> not 线程安全 -> 如何避免?
[1] `常量表达式` 初始化
[2] `无副作用的表达式` 初始化
[3] `单线程启动阶段` 初始化
[4] `互斥`
(3)替代: 返回引用 to local static 变量
[1] 常量表达式初始化 => 链接时初始化: 线程安全
[2] `递增` : not 线程安全
int& use_count()
{
static int uc = 0;
return uc;
}
void f()
{
cout << ++use_count(); // 读取并递增
}
2 程序 终止
(1) 6种 方式
————————————————————
从 main() 返回
————————————————————
调 exit()
调 quick_exit()
调 abort()
————————————————————
抛出 未捕获的异常
违反 noexcept
————————————————————
(2) 调 exit()
其 `caller 函数` 和 caller 函数中的 `local 变量`
不会执行 各自的 dtor
(3) `离开 context/上下文` 的 最好方式: `抛出异常`
main() 可 捕获 所有异常
(4) atexit()
在 `程序终止过程中 执行其 arg 所指 func`
arg: 函数指针 - paraType&returnType 均 为 空/void`
void my_cleanup(){}
atexit(&my_cleanup);
ODR.jpg
多头文件 组织 + 正交设计.jpg
网友评论