第10章 内存模型和名称空间 内存模型 内存区域 C++程序的内存通常分为以下几个区域,每个区域都有其特定的用途和管理方式:
代码区(Text Segment) :存储程序的可执行指令,通常是只读的全局/静态区(Data Segment) :存储全局变量和静态变量,分为初始化和未初始化部分常量区(Constant Area) :存储常量数据,如字符串字面量和const变量堆区(Heap) :动态分配的内存,由程序员通过new/delete或malloc/free管理栈区(Stack) :存储局部变量、函数参数和返回地址,由编译器自动管理内存映射区(Memory Mapping Segment) :用于文件映射和共享内存内存区域的底层实现 内存区域的划分是由链接器和加载器共同完成的,它们根据可执行文件的结构(如ELF或PE格式)将不同类型的数据加载到不同的内存区域:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ; x86-64 ELF内存布局示例 ; 高地址 ; ┌─────────────────────────────────────────┐ ; │ 命令行参数和环境变量 │ ; ├─────────────────────────────────────────┤ ; │ 栈区 │ ; │ (向下生长) │ ; ├─────────────────────────────────────────┤ ; │ 堆区 │ ; │ (向上生长) │ ; ├─────────────────────────────────────────┤ ; │ 全局/静态区 │ ; │ ┌─────────────────────────────────────┐ │ ; │ │ 初始化数据区(.data) │ │ ; │ ├─────────────────────────────────────┤ │ ; │ │ 未初始化数据区(.bss) │ │ ; │ └─────────────────────────────────────┘ │ ; ├─────────────────────────────────────────┤ ; │ 常量区(.rodata) │ ; ├─────────────────────────────────────────┤ ; │ 代码区(.text) │ ; └─────────────────────────────────────────┘ ; 低地址
内存区域的硬件映射 现代计算机系统使用虚拟内存技术,将程序的逻辑内存地址映射到物理内存地址:
页表机制 :操作系统使用页表将虚拟地址映射到物理地址内存保护 :通过页表项的权限位实现内存区域的访问控制内存分页 :通常以4KB或更大的页为单位管理内存TLB缓存 :CPU使用TLB(Translation Lookaside Buffer)缓存虚拟地址到物理地址的映射1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <sys/mman.h> int main () { long pageSize = sysconf (_SC_PAGESIZE); std::cout << "Page size: " << pageSize << " bytes" << std::endl; void * ptr = nullptr ; int err = posix_memalign (&ptr, pageSize, pageSize * 2 ); if (err == 0 ) { std::cout << "Aligned pointer: " << ptr << std::endl; std::cout << "Page aligned: " << ((reinterpret_cast <uintptr_t >(ptr) % pageSize) == 0 ) << std::endl; free (ptr); } return 0 ; }
内存区域的性能特性 内存区域 访问速度 管理方式 内存增长方向 典型用途 代码区 极快(指令缓存) 只读,系统管理 固定 可执行指令 常量区 快(数据缓存) 只读,系统管理 固定 常量数据 全局/静态区 快(数据缓存) 系统管理 固定 全局变量 堆区 较慢(可能缺页) 程序员管理 向上 动态数据 栈区 极快(栈缓存) 编译器管理 向下 局部变量 内存映射区 中等(可能缺页) 系统管理 固定 文件映射
内存区域的优化策略 代码区优化 :
内联函数减少函数调用开销 函数排序提高指令缓存命中率 避免过度模板实例化导致代码膨胀 数据区优化 :
合理组织全局变量,提高数据缓存命中率 使用constexpr和constinit减少运行时初始化 避免使用大的全局数组,改用动态分配 堆区优化 :
使用内存池减少内存分配开销 合理设置内存分配策略,减少内存碎片 使用智能指针自动管理内存 栈区优化 :
避免使用大的局部变量,防止栈溢出 合理使用栈空间,提高局部性 利用编译器优化,将频繁使用的变量分配到寄存器 内存布局详细信息 内存区域详解 代码区(Text Segment) :
存储内容 :程序的可执行指令,函数体的二进制代码访问权限 :通常是只读的,防止程序意外修改指令大小确定 :在编译时确定,由链接器根据代码量计算缓存优化 :支持指令缓存(L1 I-Cache),提高CPU执行效率共享特性 :可共享,多个进程可以共享同一代码段,节省内存底层实现 :对应可执行文件的.text段,加载时映射到内存性能特性 :访问速度极快,指令预取和分支预测进一步提升性能常量区(Constant Area) :
存储内容 :常量数据,如字符串字面量、const全局变量访问权限 :通常是只读的,修改会导致未定义行为存储期 :字符串字面量具有静态存储期,程序结束时才释放大小确定 :在编译时确定,由链接器计算共享特性 :与代码区类似,可被多个进程共享底层实现 :对应可执行文件的.rodata段(Read-Only Data)性能特性 :访问速度快,数据缓存(L1 D-Cache)命中率高全局/静态区(Data Segment) :
初始化数据区(.data) :存储已初始化的全局变量和静态变量未初始化数据区(.bss) :存储未初始化的全局变量和静态变量,程序启动时会被初始化为0大小确定 :在编译时确定,.data段占用可执行文件空间,.bss段不占用初始化顺序 :全局变量和静态变量的初始化顺序由编译器决定,不同编译单元间的顺序不确定底层实现 :对应可执行文件的.data和.bss段性能特性 :访问速度快,数据缓存命中率高堆区(Heap) :
存储内容 :动态分配的内存,如通过new/delete或malloc/free分配的内存生长方向 :向上生长(地址从低到高)大小管理 :大小在运行时动态变化,由内存分配器管理内存碎片 :频繁的分配和释放会导致内存碎片,降低内存利用率分配开销 :分配和释放的开销相对较大,涉及系统调用和内存分配算法底层实现 :由操作系统的内存管理器和C++运行时库共同管理性能特性 :访问速度较慢,可能导致页面错误和缓存未命中栈区(Stack) :
存储内容 :局部变量、函数参数、返回地址、寄存器状态生长方向 :向下生长(地址从高到低)大小限制 :大小在编译时确定(但可配置),通常为几MB管理方式 :由编译器自动管理,函数调用时分配,函数返回时释放分配开销 :分配和释放的开销很小,仅需要调整栈指针(rsp寄存器)异常处理 :栈溢出会导致程序崩溃(stack overflow)底层实现 :由CPU的栈指针寄存器和操作系统共同管理性能特性 :访问速度极快,栈缓存(stack cache)和寄存器优化进一步提升性能命令行参数和环境变量 :
存储内容 :命令行参数(argc/argv)和环境变量(envp)存储位置 :位于栈区之上,程序启动时由操作系统初始化访问方式 :通过main函数的参数或全局变量 environ 访问内存映射区(Memory Mapping Segment) :
存储内容 :文件映射、共享内存、动态链接库管理方式 :由操作系统通过mmap系统调用管理大小特性 :大小固定,与映射的文件或内存对象相关性能特性 :访问速度中等,可能导致页面错误底层实现 :对应操作系统的内存映射机制内存布局的底层实现 内存布局是由链接器和加载器共同确定的,它们根据可执行文件的格式(如ELF、PE)来组织内存:
加载器在加载程序时,会:
解析可执行文件 :读取ELF或PE头部,识别各个段分配内存 :为各个段分配虚拟内存空间设置权限 :为不同段设置适当的访问权限(如代码段设为只读可执行)加载数据 :将可执行文件中的数据加载到对应内存区域解析重定位 :处理符号引用,修正地址初始化环境 :设置栈指针,传递命令行参数和环境变量跳转到入口点 :执行程序的main函数内存布局的性能影响 内存布局对程序性能有显著影响,主要体现在以下几个方面:
缓存局部性 :
时间局部性 :最近访问过的数据很可能再次被访问空间局部性 :附近的数据很可能被连续访问优化策略 :合理组织数据结构,提高缓存命中率内存访问模式 :
顺序访问 :比随机访问更高效,CPU预取机制可以预测随机访问 :可能导致频繁的缓存未命中和页面错误优化策略 :使用连续存储的数据结构,如数组内存碎片 :
内部碎片 :分配的内存块大于实际需要的大小外部碎片 :内存中存在许多小的空闲块,无法满足大的分配请求优化策略 :使用内存池,合理设置分配策略内存带宽 :
内存瓶颈 :当CPU处理速度超过内存访问速度时,会出现内存瓶颈优化策略 :减少内存访问,使用缓存友好的数据结构内存布局的优化技术 数据对齐优化 :
使用alignas关键字指定内存对齐要求 合理安排结构体成员顺序,减少内存填充 确保数据对齐到缓存行边界,提高访问效率 内存池技术 :
预分配内存块,减少内存分配开销 针对特定大小的对象设计专用内存池 减少内存碎片,提高内存利用率 缓存优化 :
缓存行填充 :避免false sharing(伪共享)数据预取 :使用__builtin_prefetch或类似指令指令重排序 :让编译器优化指令顺序,提高指令缓存命中率内存分配策略 :
小对象分配 :使用线程本地缓存(TLC)大对象分配 :直接使用系统分配器批量分配 :一次性分配多个对象的内存栈使用优化 :
避免使用大的局部变量,防止栈溢出 合理使用递归,避免过深的调用栈 利用编译器优化,将频繁使用的变量分配到寄存器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 struct CacheFriendlyData { int counter; bool flag; char padding[64 - sizeof (int ) - sizeof (bool )]; double data[100 ]; }; template <typename T, size_t BlockSize = 4096 >class MemoryPool {private : struct Node { Node* next; char data[sizeof (T)]; }; Node* freeList; public : MemoryPool () : freeList (nullptr ) { char * block = new char [BlockSize]; size_t numNodes = BlockSize / sizeof (Node); for (size_t i = 0 ; i < numNodes; i++) { Node* node = reinterpret_cast <Node*>(block + i * sizeof (Node)); node->next = freeList; freeList = node; } } T* allocate () { if (!freeList) { char * block = new char [BlockSize]; size_t numNodes = BlockSize / sizeof (Node); for (size_t i = 0 ; i < numNodes; i++) { Node* node = reinterpret_cast <Node*>(block + i * sizeof (Node)); node->next = freeList; freeList = node; } } Node* node = freeList; freeList = freeList->next; return reinterpret_cast <T*>(node->data); } void deallocate (T* ptr) { Node* node = reinterpret_cast <Node*>( reinterpret_cast <char *>(ptr) - offsetof (Node, data) ); node->next = freeList; freeList = node; } };
内存布局示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 high address ┌─────────────────────────────────────────┐ │ 命令行参数和环境变量 │ ├─────────────────────────────────────────┤ │ 栈区 │ │ (向下生长) │ │ ┌─────────────────────────────────────┐ │ │ │ 函数返回地址 │ │ │ ├─────────────────────────────────────┤ │ │ │ 寄存器状态 │ │ │ ├─────────────────────────────────────┤ │ │ │ 函数参数 │ │ │ ├─────────────────────────────────────┤ │ │ │ 局部变量 │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 堆区 │ │ (向上生长) │ │ ┌─────────────────────────────────────┐ │ │ │ 动态分配的内存块 │ │ │ ├─────────────────────────────────────┤ │ │ │ 动态分配的内存块 │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 全局/静态区 │ │ ┌─────────────────────────────────────┐ │ │ │ 初始化的全局变量和静态变量 │ │ │ ├─────────────────────────────────────┤ │ │ │ 未初始化的全局变量和静态变量(BSS) │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 常量区 │ │ ┌─────────────────────────────────────┐ │ │ │ 字符串字面量 │ │ │ ├─────────────────────────────────────┤ │ │ │ const 全局变量 │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 代码区 │ │ ┌─────────────────────────────────────┐ │ │ │ 函数体的二进制代码 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘ low address
内存对齐 内存对齐是指变量在内存中的存储位置必须是其大小的整数倍,这是为了提高内存访问效率:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct Example { char c; int i; double d; }; std::cout << sizeof (Example) << std::endl; #pragma pack(push, 1) struct PackedExample { char c; int i; double d; }; #pragma pack(pop) std::cout << sizeof (PackedExample) << std::endl;
内存对齐的原理和应用 内存对齐的原理 :
CPU访问内存时,通常以字长为单位(32位CPU为4字节,64位CPU为8字节) 如果变量存储在非对齐地址,CPU需要多次访问内存才能获取完整数据 对齐存储可以减少CPU的内存访问次数,提高程序运行速度 内存对齐的规则 :
基本类型的对齐值通常等于其大小 结构体的对齐值等于其成员中最大的对齐值 每个成员的存储位置必须是其对齐值的整数倍 结构体的总大小必须是其对齐值的整数倍 内存对齐的优化策略 :
成员排序 :将小尺寸成员放在一起,减少填充空间显式对齐 :使用 alignas 关键字指定对齐值内存池设计 :确保内存池分配的内存块按适当的边界对齐避免过度对齐 :过度对齐会浪费内存空间内存对齐的现代C++支持 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 struct alignas (16 ) AlignedStruct { char c; int i; double d; }; std::cout << alignof (AlignedStruct) << std::endl; std::cout << sizeof (AlignedStruct) << std::endl; #include <memory> void * alignedAlloc (size_t size, size_t alignment) { void * ptr = nullptr ; #ifdef _WIN32 ptr = _aligned_malloc(size, alignment); #else posix_memalign (&ptr, alignment, size); #endif return ptr; } void alignedFree (void * ptr) { #ifdef _WIN32 _aligned_free(ptr); #else free (ptr); #endif } #include <new> void * allocateAligned (size_t size, size_t alignment) { return std::aligned_alloc (alignment, size); }
不同类型变量的存储位置 变量类型 存储位置 生命周期 作用域 全局变量 全局/静态区 程序开始到结束 全局 静态全局变量 全局/静态区 程序开始到结束 文件 静态局部变量 全局/静态区 程序开始到结束 局部 局部变量 栈区 函数调用到返回 局部 函数参数 栈区 函数调用到返回 函数参数 动态分配变量 堆区 手动分配到释放 指针作用域 字符串字面量 常量区 程序开始到结束 全局 const 全局变量 常量区 程序开始到结束 全局 const 局部变量 栈区(通常) 函数调用到返回 局部
内存布局的性能影响 缓存命中率 :
内存布局会影响缓存命中率 连续存储的数据结构(如数组)缓存命中率高 分散存储的数据结构(如链表)缓存命中率低 内存访问模式 :
顺序访问模式比随机访问模式更高效 合理的内存布局可以提高内存访问的局部性 内存碎片 :
频繁的动态内存分配和释放会导致内存碎片 内存碎片会降低内存利用率和程序性能 内存布局优化策略 :
使用内存池减少内存碎片 合理设计数据结构,提高缓存命中率 避免过度使用动态内存分配 利用内存对齐提高访问速度 内存布局示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 高地址 ┌─────────────────────────────────────────┐ │ 命令行参数和环境变量 │ ├─────────────────────────────────────────┤ │ 栈区 │ │ (向下生长) │ │ ┌─────────────────────────────────────┐ │ │ │ 函数返回地址 │ │ │ ├─────────────────────────────────────┤ │ │ │ 函数参数 │ │ │ ├─────────────────────────────────────┤ │ │ │ 局部变量 │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 堆区 │ │ (向上生长) │ │ ┌─────────────────────────────────────┐ │ │ │ 动态分配的内存块 │ │ │ ├─────────────────────────────────────┤ │ │ │ 动态分配的内存块 │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 全局/静态区 │ │ ┌─────────────────────────────────────┐ │ │ │ 初始化的全局变量和静态变量 │ │ │ ├─────────────────────────────────────┤ │ │ │ 未初始化的全局变量和静态变量(BSS) │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 常量区 │ │ ┌─────────────────────────────────────┐ │ │ │ 字符串字面量 │ │ │ ├─────────────────────────────────────┤ │ │ │ const 全局变量 │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 代码区 │ │ ┌─────────────────────────────────────┐ │ │ │ 函数体的二进制代码 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘ 低地址
内存对齐 内存对齐是指变量在内存中的存储位置必须是其大小的整数倍,这是为了提高内存访问效率:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct Example { char c; int i; double d; }; std::cout << sizeof (Example) << std::endl; #pragma pack(push, 1) struct PackedExample { char c; int i; double d; }; #pragma pack(pop) std::cout << sizeof (PackedExample) << std::endl;
不同类型变量的存储位置 变量类型 存储位置 生命周期 作用域 全局变量 全局/静态区 程序开始到结束 全局 静态全局变量 全局/静态区 程序开始到结束 文件 静态局部变量 全局/静态区 程序开始到结束 局部 局部变量 栈区 函数调用到返回 局部 函数参数 栈区 函数调用到返回 函数参数 动态分配变量 堆区 手动分配到释放 指针作用域 字符串字面量 常量区 程序开始到结束 全局 const 全局变量 常量区 程序开始到结束 全局 const 局部变量 栈区(通常) 函数调用到返回 局部
存储类别 auto 存储类别 默认存储类别 :局部变量的默认存储类别生命周期 :函数调用时创建,函数返回时销毁作用域 :局部作用域(定义它的代码块)可见性 :只在定义它的代码块内可见特点 :存储在栈区,分配和释放开销很小1 2 3 4 void function () { auto int x = 10 ; int y = 20 ; }
static 存储类别 生命周期 :程序开始时创建,程序结束时销毁作用域 :可见性 :全局静态变量:只在定义它的文件内可见 局部静态变量:只在定义它的代码块内可见 存储位置 :全局/静态区特点 :初始化只执行一次,后续调用会保留上次的值1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static int globalStatic = 100 ;void function () { static int localStatic = 0 ; localStatic++; std::cout << "Local static: " << localStatic << std::endl; } int main () { function (); function (); function (); return 0 ; }
static 存储类别的高级应用 单例模式 :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Singleton {public : static Singleton& getInstance () { static Singleton instance; return instance; } void doSomething () { std::cout << "Singleton doing something" << std::endl; } private : Singleton () {} ~Singleton () {} Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; }; int main () { Singleton& singleton = Singleton::getInstance (); singleton.doSomething (); return 0 ; }
函数内的静态缓存 :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <vector> std::vector<int > generateLargeData () { std::vector<int > data (1000000 ) ; for (int i = 0 ; i < data.size (); i++) { data[i] = i; } return data; } const std::vector<int >& getCachedData () { static const std::vector<int > cachedData = generateLargeData (); return cachedData; } void processData () { const auto & data = getCachedData (); }
extern 存储类别 生命周期 :程序开始时创建,程序结束时销毁作用域 :全局作用域可见性 :整个程序可见(通过声明)存储位置 :全局/静态区特点 :用于在多个文件间共享变量1 2 3 4 5 6 7 8 9 extern int globalVar; void function () { std::cout << globalVar << std::endl; } int globalVar = 100 ;
extern 存储类别的最佳实践 头文件声明 :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #pragma once extern const int MAX_CONNECTIONS;extern const char * DEFAULT_CONFIG_FILE;#include "config.h" const int MAX_CONNECTIONS = 100 ;const char * DEFAULT_CONFIG_FILE = "config.json" ;#include "config.h" int main () { std::cout << "Max connections: " << MAX_CONNECTIONS << std::endl; std::cout << "Default config file: " << DEFAULT_CONFIG_FILE << std::endl; return 0 ; }
避免使用全局变量 :优先使用函数封装全局状态 使用命名空间组织全局变量 考虑使用单例模式替代全局变量 register 存储类别 生命周期 :同 auto 存储类别作用域 :同 auto 存储类别可见性 :同 auto 存储类别特点 :建议编译器将变量存储在寄存器中,以提高访问速度现代C++中的地位 :已被编译器自动优化取代,C++17中已弃用1 2 3 4 5 6 7 void function () { register int counter = 0 ; for (int i = 0 ; i < 1000000 ; i++) { counter++; } }
注意 :现代编译器会自动优化变量存储,register 关键字的作用已经不大。编译器会根据变量的使用情况自动决定是否将其存储在寄存器中。
C++20新特性:constinit和consteval constinit关键字 constinit关键字确保变量在编译时初始化,避免了静态初始化顺序问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 constinit int global_value = 42 ;void function () { static constinit int static_value = 100 ; } constinit int counter = 0 ;void increment () { counter++; }
consteval关键字 consteval关键字用于函数,表示该函数必须在编译时执行:
1 2 3 4 5 6 7 8 9 10 11 consteval int factorial (int n) { return n <= 1 ? 1 : n * factorial (n - 1 ); } constexpr int result = factorial (5 );
现代C++存储类别的最佳实践 优先使用自动存储 :
局部变量使用默认的自动存储 减少静态变量的使用,避免全局状态 合理使用静态存储 :
用于单例模式 用于函数内的缓存 用于需要跨函数调用保持状态的场景 避免使用全局变量 :
使用命名空间组织全局变量 使用函数封装全局状态 考虑使用依赖注入替代全局变量 利用现代C++特性 :
使用 constexpr 和 consteval 进行编译期计算 使用 constinit 避免静态初始化顺序问题 使用智能指针管理动态内存 性能优化考虑 :
频繁访问的变量可能会被编译器自动存储在寄存器中 静态变量的初始化开销只发生一次 自动变量的分配和释放开销很小 存储类别与性能 自动存储(栈) :
优点 :分配和释放开销小,访问速度快缺点 :作用域有限,栈空间有限适用场景 :局部变量,函数参数,临时对象静态存储(全局/静态区) :
优点 :生命周期长,初始化只执行一次缺点 :全局可见性可能导致命名冲突,多线程环境下需要同步适用场景 :单例模式,缓存数据,配置参数动态存储(堆) :
优点 :大小灵活,生命周期可控缺点 :分配和释放开销大,可能产生内存碎片适用场景 :大型数据结构,运行时大小不确定的对象寄存器存储 :
优点 :访问速度极快缺点 :数量有限,编译器自动管理适用场景 :频繁使用的变量,循环计数器存储类别与线程安全 自动存储 :
线程安全,每个线程有自己的栈 局部变量不会被其他线程访问 静态存储 :
非线程安全,多个线程共享同一个静态变量 需要使用互斥锁或原子操作保证线程安全 动态存储 :
线程安全取决于内存的使用方式 多个线程访问同一块堆内存需要同步 现代C++的线程安全实践 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <mutex> #include <atomic> class ThreadSafeCounter {private : static int count; static std::mutex mtx; public : static void increment () { std::lock_guard<std::mutex> lock (mtx) ; count++; } static int getCount () { std::lock_guard<std::mutex> lock (mtx) ; return count; } }; int ThreadSafeCounter::count = 0 ;std::mutex ThreadSafeCounter::mtx; class AtomicCounter {private : static std::atomic<int > count; public : static void increment () { count++; } static int getCount () { return count; } }; std::atomic<int > AtomicCounter::count (0 ) ;
作用域 全局作用域 定义 :在所有函数外部定义的标识符可见性 :从定义点开始,到文件结束生命周期 :程序开始到程序结束存储位置 :全局/静态区链接性 :外部链接性(默认),可在其他文件中访问1 2 3 4 5 6 7 8 9 10 11 int globalVar = 100 ; void function () { std::cout << globalVar << std::endl; } int main () { std::cout << globalVar << std::endl; function (); return 0 ; }
局部作用域 定义 :在函数内部定义的标识符可见性 :从定义点开始,到函数结束生命周期 :函数调用开始到函数返回存储位置 :栈区(默认)链接性 :无链接性,只在函数内部可见1 2 3 4 5 6 7 8 9 10 void function () { int localVar = 50 ; std::cout << localVar << std::endl; } int main () { function (); return 0 ; }
块作用域 定义 :在代码块内部定义的标识符可见性 :从定义点开始,到代码块结束生命周期 :代码块开始执行到代码块执行结束存储位置 :栈区(默认)链接性 :无链接性,只在代码块内部可见1 2 3 4 5 6 7 8 void function () { int x = 10 ; { int y = 20 ; std::cout << x << " " << y << std::endl; } }
函数原型作用域 定义 :函数原型中的参数名可见性 :只在函数原型中可见生命周期 :无(仅在编译时有效)链接性 :无链接性1 2 3 4 5 void function (int x) ; void function (int y) { std::cout << y << std::endl; }
类作用域 定义 :类的成员可见性 :通过类的对象、指针、引用或类名访问生命周期 :取决于成员的存储类别存储位置 :非静态成员:对象内部(堆或栈) 静态成员:全局/静态区 链接性 :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MyClass {public : int memberVar; static int staticVar; void memberFunc () { std::cout << memberVar << std::endl; std::cout << staticVar << std::endl; } }; int MyClass::staticVar = 200 ; int main () { MyClass obj; obj.memberVar = 100 ; obj.memberFunc (); std::cout << MyClass::staticVar << std::endl; return 0 ; }
命名空间作用域 定义 :在命名空间中定义的标识符可见性 :通过命名空间名、using 声明或 using 指令访问生命周期 :取决于标识符的存储类别存储位置 :取决于标识符的存储类别链接性 :外部链接性(默认),可在其他文件中访问1 2 3 4 5 6 7 8 9 10 11 12 13 namespace MyNamespace { int namespaceVar = 200 ; void namespaceFunc () { std::cout << "MyNamespace::namespaceFunc()" << std::endl; } } int main () { std::cout << MyNamespace::namespaceVar << std::endl; MyNamespace::namespaceFunc (); return 0 ; }
作用域的嵌套与名称隐藏 作用域嵌套 :内部作用域可以访问外部作用域的标识符名称隐藏 :内部作用域中的标识符会隐藏外部作用域中同名的标识符1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int x = 100 ; void function () { int x = 200 ; std::cout << x << std::endl; std::cout << ::x << std::endl; { int x = 300 ; std::cout << x << std::endl; } } int main () { function (); return 0 ; }
名称查找规则 C++中的名称查找遵循以下规则:
局部作用域 :首先在当前作用域查找外层作用域 :如果局部作用域没有找到,在外层作用域查找命名空间作用域 :如果外层作用域没有找到,在使用的命名空间中查找全局作用域 :如果命名空间作用域没有找到,在全局作用域查找:: 作用域 :如果指定了作用域解析运算符,则在指定的作用域中查找名称查找的详细过程 非限定名称查找 :
从当前作用域开始,向外层作用域查找 找到第一个匹配的名称后停止 不会查找其他作用域中的同名名称 限定名称查找 :
从指定的作用域开始查找 遵循作用域的嵌套规则 用于访问命名空间、类的成员 依赖名称查找 :
用于模板中的依赖名称 查找过程在模板实例化时进行 会考虑模板参数的作用域 作用域与性能 局部变量的性能优势 :
存储在栈区,分配和释放开销小 编译器容易优化,可能存储在寄存器中 访问速度快 全局变量的性能劣势 :
存储在全局/静态区,访问速度相对较慢 可能导致缓存未命中 多线程环境下需要同步,增加开销 作用域对优化的影响 :
窄作用域的变量更容易被编译器优化 编译器可以更准确地分析变量的使用情况 减少变量的生命周期,释放资源更快 作用域的最佳实践 最小作用域原则 :
将变量定义在尽可能小的作用域内 减少变量的可见范围,提高代码可读性和安全性 便于编译器优化 避免全局变量 :
使用命名空间组织全局变量 使用函数封装全局状态 考虑使用单例模式或依赖注入替代全局变量 合理使用块作用域 :
使用块作用域限制临时变量的生命周期 提高代码的可读性和可维护性 便于资源管理(RAII) 命名空间的最佳实践 :
使用有意义的命名空间名称 避免过度嵌套命名空间 在头文件中使用完全限定名称,避免 using 指令 现代C++中的作用域特性 :
使用 lambda 表达式创建匿名作用域 使用局部类限制类的作用域 使用 inline 命名空间简化版本管理 作用域与资源管理 RAII与作用域 :
利用块作用域自动管理资源 资源在构造时获取,在作用域结束时释放 提高代码的安全性和可维护性 智能指针与作用域 :
智能指针的生命周期由作用域控制 离开作用域时自动释放内存 避免内存泄漏和悬空指针 作用域与异常安全 :
即使发生异常,作用域结束时也会释放资源 提高代码的鲁棒性 减少异常处理的复杂度 现代C++中的作用域特性 Lambda表达式的作用域 :1 2 3 4 5 6 7 8 9 10 void function () { int x = 10 ; auto lambda = [x](int y) { return x + y; }; std::cout << lambda (20 ) << std::endl; }
局部类 :1 2 3 4 5 6 7 8 9 10 11 12 void function () { class LocalClass { public : void doSomething () { std::cout << "LocalClass::doSomething()" << std::endl; } }; LocalClass obj; obj.doSomething (); }
内联命名空间 :1 2 3 4 5 6 7 8 9 10 11 12 inline namespace V2 { void function () { std::cout << "V2::function()" << std::endl; } } int main () { function (); return 0 ; }
链接性 外部链接性 定义 :可以在多个文件中访问的标识符示例 :非静态的全局变量、非静态的全局函数、类、结构体、枚举特点 :可以通过 extern 声明在其他文件中访问存储位置 :全局/静态区(变量)、代码区(函数)生命周期 :程序开始到程序结束1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int globalVar = 100 ; void globalFunction () { std::cout << "globalFunction()" << std::endl; } class GlobalClass { public : void doSomething () { std::cout << "GlobalClass::doSomething()" << std::endl; } }; extern int globalVar; extern void globalFunction () ; extern class GlobalClass ; void function () { std::cout << globalVar << std::endl; globalFunction (); GlobalClass obj; obj.doSomething (); }
内部链接性 定义 :只能在定义它的文件中访问的标识符示例 :静态全局变量、静态全局函数、匿名命名空间中的标识符特点 :不可以在其他文件中访问,即使使用 extern 声明存储位置 :全局/静态区(变量)、代码区(函数)生命周期 :程序开始到程序结束1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int staticGlobalVar = 200 ; static void staticGlobalFunction () { std::cout << "staticGlobalFunction()" << std::endl; } namespace { int anonymousVar = 300 ; void anonymousFunction () { std::cout << "anonymousFunction()" << std::endl; } } extern int staticGlobalVar; extern void staticGlobalFunction () ; extern int anonymousVar;
无链接性 定义 :只能在定义它的作用域中访问的标识符示例 :局部变量、函数参数、局部类、块作用域中的变量特点 :不可以在其他作用域中访问存储位置 :栈区(默认)生命周期 :作用域开始到作用域结束1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void function () { int localVar = 300 ; class LocalClass { public : void doSomething () { std::cout << "LocalClass::doSomething()" << std::endl; } }; LocalClass obj; obj.doSomething (); } int main () { function (); return 0 ; }
链接性与作用域的关系 作用域 :描述标识符在代码中的可见范围链接性 :描述标识符在不同文件中的可见范围关系 :作用域是链接性的基础,链接性是作用域的扩展标识符类型 作用域 链接性 存储位置 生命周期 非静态全局变量 全局 外部 全局/静态区 程序开始到结束 静态全局变量 全局 内部 全局/静态区 程序开始到结束 非静态全局函数 全局 外部 代码区 程序开始到结束 静态全局函数 全局 内部 代码区 程序开始到结束 局部变量 局部 无 栈区 函数调用到返回 静态局部变量 局部 内部 全局/静态区 程序开始到结束 块作用域变量 块 无 栈区 块开始到结束 函数参数 函数参数 无 栈区 函数调用到返回
链接性的现代C++实践 使用命名空间组织代码 :
替代静态全局变量和函数 提供更好的代码组织和命名隔离 支持嵌套和版本管理 使用匿名命名空间 :
替代静态全局变量和函数 提供更好的代码隔离 支持C++的作用域规则 避免使用全局变量 :
使用命名空间组织全局变量 使用函数封装全局状态 考虑使用单例模式或依赖注入 链接性与模板 :
模板的链接性取决于其实例化 模板通常需要在头文件中定义 可以使用显式实例化控制链接性 链接性与性能 外部链接性的性能影响 :
全局变量可能导致缓存未命中 多线程环境下需要同步,增加开销 编译器优化难度增加 内部链接性的性能优势 :
编译器可以更自由地优化静态函数 减少名称冲突的可能性 提高代码的可维护性 无链接性的性能优势 :
局部变量容易被编译器优化 可能存储在寄存器中 访问速度快 链接性的最佳实践 最小链接性原则 :
尽量使用最小的链接性 优先使用无链接性(局部变量) 其次使用内部链接性(静态变量、匿名命名空间) 最后使用外部链接性(全局变量、函数) 命名空间的使用 :
使用有意义的命名空间名称 避免过度嵌套命名空间 在头文件中使用完全限定名称 静态变量的使用 :
用于文件内部的状态管理 用于函数内的缓存 避免使用静态变量存储全局状态 匿名命名空间的使用 :
替代静态全局变量和函数 提供更好的代码隔离 支持C++的作用域规则 现代C++中的链接性特性 内联函数 :
可以在多个文件中定义 编译器会选择一个定义 避免了链接错误 模板 :
通常需要在头文件中定义 实例化时会生成具体的函数或类 可以使用显式实例化控制链接性 内联命名空间 :
提供版本管理的能力 可以直接访问内联命名空间的内容 支持平滑的API演进 模块系统 :
C++20引入的模块系统 提供更好的代码组织和隔离 减少了头文件的依赖 提高了编译速度 链接性与异常安全 全局变量的异常安全 :
全局变量的构造函数抛出异常会导致程序终止 应该避免在全局变量的构造函数中进行可能抛出异常的操作 静态变量的异常安全 :
静态变量的构造函数抛出异常会导致程序终止 应该避免在静态变量的构造函数中进行可能抛出异常的操作 局部静态变量的异常安全 :
C++11之后,局部静态变量的初始化是线程安全的 如果构造函数抛出异常,下次调用时会重新尝试初始化 链接性与线程安全 全局变量的线程安全 :
多个线程同时访问全局变量需要同步 应该使用互斥锁或原子操作保护全局变量 静态变量的线程安全 :
多个线程同时访问静态变量需要同步 应该使用互斥锁或原子操作保护静态变量 局部静态变量的线程安全 :
C++11之后,局部静态变量的初始化是线程安全的 多个线程同时调用包含局部静态变量的函数不会导致初始化竞争 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Singleton {public : static Singleton& getInstance () { static Singleton instance; return instance; } void doSomething () { std::cout << "Singleton::doSomething()" << std::endl; } private : Singleton () {} ~Singleton () {} Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; }; class Config {private : static std::mutex mtx; static std::unique_ptr<Config> instance; public : static Config& getInstance () { std::lock_guard<std::mutex> lock (mtx) ; if (!instance) { instance = std::make_unique <Config>(); } return *instance; } void setValue (const std::string& key, const std::string& value) { std::lock_guard<std::mutex> lock (mtx) ; } std::string getValue (const std::string& key) { std::lock_guard<std::mutex> lock (mtx) ; return "" ; } }; std::mutex Config::mtx; std::unique_ptr<Config> Config::instance = nullptr ;
动态内存管理 堆内存分配 new 运算符的底层实现 new 运算符在C++中执行以下操作:
调用 operator new 分配内存 调用对象的构造函数 返回指向分配内存的指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void * operator new (size_t size) { void * ptr = std::malloc (size); if (!ptr) { throw std::bad_alloc (); } return ptr; } int * pInt = new int ;*pInt = 100 ; std::cout << *pInt << std::endl; int * pInt2 = new int (200 );std::cout << *pInt2 << std::endl; int * pArray = new int [5 ];for (int i = 0 ; i < 5 ; i++) { pArray[i] = i; } int * pArray2 = new int [5 ]{1 , 2 , 3 , 4 , 5 };class MyClass {public : MyClass (int v) : value (v) { std::cout << "MyClass constructor called" << std::endl; } ~MyClass () { std::cout << "MyClass destructor called" << std::endl; } int value; }; MyClass* pObj = new MyClass (300 ); std::cout << pObj->value << std::endl;
delete 运算符的底层实现 delete 运算符在C++中执行以下操作:
调用对象的析构函数 调用 operator delete 释放内存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void operator delete (void * ptr) noexcept { std::free (ptr); } delete pInt;pInt = nullptr ; delete [] pArray;pArray = nullptr ; delete pObj;pObj = nullptr ;
自定义内存分配器 标准分配器接口实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 template <typename T>class CustomAllocator {public : using value_type = T; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; using size_type = std::size_t ; using difference_type = std::ptrdiff_t ; CustomAllocator () noexcept = default ; template <typename U> CustomAllocator (const CustomAllocator<U>&) noexcept {} pointer allocate (size_type n) { if (n > max_size ()) { throw std::bad_alloc (); } void * ptr = std::malloc (n * sizeof (T)); if (!ptr) { throw std::bad_alloc (); } return static_cast <pointer>(ptr); } void deallocate (pointer p, size_type) noexcept { std::free (p); } size_type max_size () const noexcept { return std::numeric_limits<size_type>::max () / sizeof (T); } template <typename U, typename ... Args> void construct (U* p, Args&&... args) { ::new (static_cast <void *>(p)) U (std::forward<Args>(args)...); } template <typename U> void destroy (U* p) { p->~U (); } template <typename U> struct rebind { using other = CustomAllocator<U>; }; }; template <typename T, typename U>bool operator ==(const CustomAllocator<T>&, const CustomAllocator<U>&) noexcept { return true ; } template <typename T, typename U>bool operator !=(const CustomAllocator<T>&, const CustomAllocator<U>&) noexcept { return false ; } void useCustomAllocator () { std::vector<int , CustomAllocator<int >> vec; for (int i = 0 ; i < 10 ; ++i) { vec.push_back (i); } struct MyType { int value; MyType (int v) : value (v) {} }; std::vector<MyType, CustomAllocator<MyType>> customVec; for (int i = 0 ; i < 10 ; ++i) { customVec.emplace_back (i * 10 ); } }
内存池分配器实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 class MemoryPool {private : struct Block { Block* next; char data[1 ]; }; Block* freeList; size_t blockSize; size_t alignment; Block* allocateBlock () { size_t actualSize = sizeof (Block) + blockSize - 1 ; void * raw = std::malloc (actualSize); if (!raw) { throw std::bad_alloc (); } void * aligned = raw; if (alignment > 1 ) { uintptr_t addr = reinterpret_cast <uintptr_t >(raw); uintptr_t alignedAddr = (addr + alignment - 1 ) & ~(alignment - 1 ); aligned = reinterpret_cast <void *>(alignedAddr); } Block* block = reinterpret_cast <Block*>(aligned); block->next = nullptr ; return block; } public : MemoryPool (size_t blockSize, size_t alignment = alignof (std::max_align_t )) : freeList (nullptr ), blockSize (blockSize), alignment (alignment) { } ~MemoryPool () { while (freeList) { Block* next = freeList->next; std::free (freeList); freeList = next; } } void * allocate () { if (!freeList) { freeList = allocateBlock (); } void * ptr = freeList->data; freeList = freeList->next; return ptr; } void deallocate (void * ptr) { if (!ptr) return ; Block* block = reinterpret_cast <Block*>( reinterpret_cast <char *>(ptr) - offsetof (Block, data) ); block->next = freeList; freeList = block; } }; template <typename T>class PoolAllocator {private : static MemoryPool pool; public : using value_type = T; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; using size_type = std::size_t ; using difference_type = std::ptrdiff_t ; PoolAllocator () noexcept = default ; template <typename U> PoolAllocator (const PoolAllocator<U>&) noexcept {} pointer allocate (size_type n) { if (n != 1 ) { throw std::bad_alloc (); } return static_cast <pointer>(pool.allocate ()); } void deallocate (pointer p, size_type) noexcept { pool.deallocate (p); } template <typename U> struct rebind { using other = PoolAllocator<U>; }; }; template <typename T>MemoryPool PoolAllocator<T>::pool (sizeof (T)); void usePoolAllocator () { std::vector<int , PoolAllocator<int >> vec; for (int i = 0 ; i < 10 ; ++i) { vec.push_back (i); } }
线程本地缓存分配器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 class ThreadLocalAllocator {private : struct Block { Block* next; }; static constexpr size_t BLOCK_SIZE = 64 ; static constexpr size_t MAX_CACHE_SIZE = 1024 ; thread_local static Block* cache; thread_local static size_t cacheSize; static void * allocateFromSystem (size_t size) { return std::malloc (size); } static void deallocateToSystem (void * ptr) { std::free (ptr); } public : static void * allocate (size_t size) { if (size > BLOCK_SIZE) { return allocateFromSystem (size); } if (!cache) { const size_t batchSize = 16 ; void * batch = allocateFromSystem (BLOCK_SIZE * batchSize); if (!batch) { throw std::bad_alloc (); } for (size_t i = 0 ; i < batchSize; ++i) { Block* block = reinterpret_cast <Block*>( static_cast <char *>(batch) + i * BLOCK_SIZE ); block->next = cache; cache = block; } cacheSize += batchSize; } Block* block = cache; cache = cache->next; cacheSize--; return block; } static void deallocate (void * ptr, size_t size) { if (size > BLOCK_SIZE) { deallocateToSystem (ptr); return ; } if (cacheSize < MAX_CACHE_SIZE) { Block* block = reinterpret_cast <Block*>(ptr); block->next = cache; cache = block; cacheSize++; } else { deallocateToSystem (ptr); } } }; thread_local void * ThreadLocalAllocator::cache = nullptr ;thread_local size_t ThreadLocalAllocator::cacheSize = 0 ;class MyClassWithTLAlloc {public : MyClassWithTLAlloc (int v) : value (v) {} ~MyClassWithTLAlloc () {} static void * operator new (size_t size) { return ThreadLocalAllocator::allocate (size); } static void operator delete (void * ptr, size_t size) noexcept { ThreadLocalAllocator::deallocate (ptr, size); } int value; }; void useThreadLocalAllocator () { std::vector<std::thread> threads; for (int i = 0 ; i < 4 ; ++i) { threads.emplace_back ([] { for (int j = 0 ; j < 1000 ; ++j) { MyClassWithTLAlloc* p = new MyClassWithTLAlloc (j); delete p; } }); } for (auto & thread : threads) { thread.join (); } };
常见的动态内存错误 内存泄漏 内存泄漏是指动态分配的内存没有被释放,导致内存使用量不断增加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void function () { int * p = new int (100 ); } void function () { int * p = new int (100 ); delete p; p = nullptr ; } void functionWithSmartPtr () { std::unique_ptr<int > p = std::make_unique <int >(100 ); }
悬空指针 悬空指针是指指向已释放内存的指针,使用悬空指针会导致未定义行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int * p = new int (100 );delete p;std::cout << *p << std::endl; int * p = new int (100 );delete p;p = nullptr ; if (p != nullptr ) { std::cout << *p << std::endl; } void functionWithSmartPtr () { std::unique_ptr<int > p = std::make_unique <int >(100 ); }
重复释放 重复释放是指对同一块内存释放多次,会导致未定义行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int * p = new int (100 );delete p;delete p; int * p = new int (100 );delete p;p = nullptr ; delete p; void functionWithSmartPtr () { std::unique_ptr<int > p = std::make_unique <int >(100 ); }
数组和单个对象释放混淆 使用 delete 释放数组或使用 delete[] 释放单个对象会导致未定义行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int * p = new int [5 ];delete p; int * p = new int ;delete [] p; int * pArray = new int [5 ];delete [] pArray; int * pObj = new int ;delete pObj; void functionWithSmartPtr () { std::unique_ptr<int > p1 = std::make_unique <int >(100 ); std::unique_ptr<int []> p2 = std::make_unique <int []>(5 ); }
内存碎片 内存碎片是指频繁分配和释放小块内存导致的内存空间不连续,降低内存利用率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 void fragmentMemory () { for (int i = 0 ; i < 1000 ; i++) { int * p = new int [10 ]; delete [] p; } } class MemoryPool {private : std::vector<void *> blocks; size_t blockSize; size_t currentBlock; size_t currentPosition; public : MemoryPool (size_t blockSize, size_t numBlocks) : blockSize (blockSize), currentBlock (0 ), currentPosition (0 ) { blocks.reserve (numBlocks); for (size_t i = 0 ; i < numBlocks; i++) { blocks.push_back (std::malloc (blockSize)); } } ~MemoryPool () { for (void * block : blocks) { std::free (block); } } void * allocate (size_t size) { if (size > blockSize) { throw std::bad_alloc (); } if (currentBlock >= blocks.size ()) { throw std::bad_alloc (); } void * ptr = static_cast <char *>(blocks[currentBlock]) + currentPosition; currentPosition += size; if (currentPosition + size > blockSize) { currentBlock++; currentPosition = 0 ; } return ptr; } void deallocate (void * ptr) { } }; void useMemoryPool () { MemoryPool pool (sizeof (int ) * 10 , 100 ) ; for (int i = 0 ; i < 1000 ; i++) { int * p = static_cast <int *>(pool.allocate (sizeof (int ) * 10 )); pool.deallocate (p); } }
智能指针(C++11+) unique_ptr:独占所有权 std::unique_ptr 是一种独占所有权的智能指针,同一时间只能有一个 unique_ptr 指向同一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <memory> std::unique_ptr<int > p1 (new int (100 )) ;std::cout << *p1 << std::endl; std::unique_ptr<int > p2 = std::move (p1); if (p1) { std::cout << *p1 << std::endl; } else { std::cout << "p1 is empty" << std::endl; } std::cout << *p2 << std::endl; auto p3 = std::make_unique <int >(200 );std::cout << *p3 << std::endl; auto pArray = std::make_unique <int []>(5 );for (int i = 0 ; i < 5 ; i++) { pArray[i] = i; } auto customDeleter = [](int * p) { std::cout << "Custom deleter called" << std::endl; delete p; }; std::unique_ptr<int , decltype (customDeleter) > p4 (new int (300 ), customDeleter) ;
shared_ptr:共享所有权 std::shared_ptr 是一种共享所有权的智能指针,使用引用计数来跟踪有多少个 shared_ptr 指向同一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 std::shared_ptr<int > p1 (new int (100 )) ;std::cout << *p1 << std::endl; std::cout << "Use count: " << p1. use_count () << std::endl; std::shared_ptr<int > p2 = p1; std::cout << "Use count: " << p1. use_count () << std::endl; std::cout << "Use count: " << p2. use_count () << std::endl; auto p3 = std::make_shared <int >(200 );std::cout << *p3 << std::endl; std::cout << "Use count: " << p3. use_count () << std::endl; auto customDeleter = [](int * p) { std::cout << "Custom deleter called" << std::endl; delete p; }; std::shared_ptr<int > p4 (new int (300 ), customDeleter) ;std::cout << "Use count: " << p4. use_count () << std::endl;
weak_ptr:非拥有观察者 std::weak_ptr 是一种不增加引用计数的智能指针,用于解决 shared_ptr 的循环引用问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 std::shared_ptr<int > p1 = std::make_shared <int >(100 ); std::weak_ptr<int > wp = p1; std::cout << "Use count: " << p1. use_count () << std::endl; if (auto p2 = wp.lock ()) { std::cout << *p2 << std::endl; std::cout << "Use count: " << p2. use_count () << std::endl; } else { std::cout << "wp is expired" << std::endl; } p1. reset (); if (auto p2 = wp.lock ()) { std::cout << *p2 << std::endl; } else { std::cout << "wp is expired" << std::endl; } class Node {public : std::shared_ptr<Node> next; std::weak_ptr<Node> prev; ~Node () { std::cout << "Node destructor called" << std::endl; } }; void testCircularReference () { auto node1 = std::make_shared <Node>(); auto node2 = std::make_shared <Node>(); node1->next = node2; node2->prev = node1; }
智能指针的高级应用 自定义删除器的高级用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 class DatabaseConnection {public : DatabaseConnection (const std::string& connStr) : connStr (connStr) { std::cout << "Connecting to database: " << connStr << std::endl; } void execute (const std::string& query) { std::cout << "Executing query: " << query << std::endl; } void close () { std::cout << "Closing database connection: " << connStr << std::endl; } private : std::string connStr; }; class DatabaseDeleter {private : std::string connectionName; bool logDetails; public : DatabaseDeleter (const std::string& name, bool log = true ) : connectionName (name), logDetails (log) {} void operator () (DatabaseConnection* conn) { if (conn) { if (logDetails) { std::cout << "Deleter for " << connectionName << " closing connection" << std::endl; } conn->close (); delete conn; } } }; typedef std::unique_ptr<DatabaseConnection, DatabaseDeleter> DbConnectionPtr;DbConnectionPtr createDatabaseConnection (const std::string& connStr, const std::string& name) { return DbConnectionPtr ( new DatabaseConnection (connStr), DatabaseDeleter (name) ); } auto socketDeleter = [](int * socket) { if (socket) { std::cout << "Closing socket: " << *socket << std::endl; delete socket; } }; typedef std::unique_ptr<int , decltype (socketDeleter)> SocketPtr;class ResourceManager {public : static void deleteResource (void * resource) { std::cout << "Deleting resource at: " << resource << std::endl; std::free (resource); } }; typedef std::unique_ptr<void , decltype (&ResourceManager::deleteResource)> ResourcePtr;void advancedDeletersExample () { auto dbConn = createDatabaseConnection ("localhost:5432" , "main_db" ); dbConn->execute ("SELECT * FROM users" ); SocketPtr socket (new int (42 ), socketDeleter) ; void * buffer = std::malloc (1024 ); ResourcePtr resource (buffer, &ResourceManager::deleteResource) ; }
智能指针与内存池整合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 class MemoryPool {private : struct Block { Block* next; char data[1 ]; }; Block* freeList; size_t blockSize; Block* allocateBlock () { size_t actualSize = sizeof (Block) + blockSize - 1 ; void * raw = std::malloc (actualSize); if (!raw) { throw std::bad_alloc (); } Block* block = reinterpret_cast <Block*>(raw); block->next = nullptr ; return block; } public : MemoryPool (size_t blockSize) : freeList (nullptr ), blockSize (blockSize) {} ~MemoryPool () { while (freeList) { Block* next = freeList->next; std::free (freeList); freeList = next; } } void * allocate () { if (!freeList) { freeList = allocateBlock (); } void * ptr = freeList->data; freeList = freeList->next; return ptr; } void deallocate (void * ptr) { if (!ptr) return ; Block* block = reinterpret_cast <Block*>( reinterpret_cast <char *>(ptr) - offsetof (Block, data) ); block->next = freeList; freeList = block; } template <typename T> static MemoryPool& getInstance () { static MemoryPool pool (sizeof (T)) ; return pool; } }; template <typename T>class PoolDeleter {public : void operator () (T* ptr) { if (ptr) { ptr->~T (); MemoryPool::getInstance <T>().deallocate (ptr); } } }; template <typename T>class PoolAllocator {public : using value_type = T; PoolAllocator () noexcept = default ; template <typename U> PoolAllocator (const PoolAllocator<U>&) noexcept {} T* allocate (std::size_t n) { if (n != 1 ) { throw std::bad_alloc (); } void * ptr = MemoryPool::getInstance <T>().allocate (); return static_cast <T*>(ptr); } void deallocate (T* ptr, std::size_t ) noexcept { if (ptr) { MemoryPool::getInstance <T>().deallocate (ptr); } } }; template <typename T>typedef std::unique_ptr<T, PoolDeleter<T>> PoolUniquePtr;template <typename T, typename ... Args>PoolUniquePtr<T> makePoolUnique (Args&&... args) { void * memory = MemoryPool::getInstance <T>().allocate (); T* object = new (memory) T (std::forward<Args>(args)...); return PoolUniquePtr <T>(object); } class HeavyObject {private : std::vector<int > data; std::string name; public : HeavyObject (int size, const std::string& name) : data (size), name (name) { std::cout << "Creating HeavyObject: " << name << " with size: " << size << std::endl; } ~HeavyObject () { std::cout << "Destroying HeavyObject: " << name << std::endl; } void process () { std::cout << "Processing HeavyObject: " << name << std::endl; } }; void smartPointerWithMemoryPool () { auto obj1 = makePoolUnique <HeavyObject>(1000 , "obj1" ); auto obj2 = makePoolUnique <HeavyObject>(2000 , "obj2" ); obj1->process (); obj2->process (); }
智能指针的性能优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 void optimizeSharedPtrUsage () { auto createExpensiveObject = []() -> std::unique_ptr<HeavyObject> { return std::make_unique <HeavyObject>(10000 , "expensive" ); }; auto obj = createExpensiveObject (); std::shared_ptr<HeavyObject> sharedObj = std::move (obj); } void useMakeShared () { auto sharedObj = std::make_shared <HeavyObject>(5000 , "shared" ); std::shared_ptr<HeavyObject> sharedObj2 (new HeavyObject(5000 , "shared2" )) ; } void breakCircularReference () { class Node { public : std::string name; std::shared_ptr<Node> next; std::weak_ptr<Node> prev; Node (const std::string& name) : name (name) { std::cout << "Creating Node: " << name << std::endl; } ~Node () { std::cout << "Destroying Node: " << name << std::endl; } }; auto node1 = std::make_shared <Node>("node1" ); auto node2 = std::make_shared <Node>("node2" ); node1->next = node2; node2->prev = node1; } void useSmartPointerArrays () { std::unique_ptr<int []> intArray (new int [10 ]) ; for (int i = 0 ; i < 10 ; ++i) { intArray[i] = i * 2 ; } auto doubleArray = std::make_unique <double []>(5 ); for (int i = 0 ; i < 5 ; ++i) { doubleArray[i] = i * 1.5 ; } }
智能指针与多线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <thread> #include <atomic> class SharedResource {private : std::atomic<int > refCount{0 }; std::string name; public : SharedResource (const std::string& name) : name (name) { std::cout << "Creating SharedResource: " << name << std::endl; } ~SharedResource () { std::cout << "Destroying SharedResource: " << name << std::endl; } void use () { int currentCount = refCount.fetch_add (1 ); std::cout << "Using " << name << " (ref count: " << currentCount + 1 << ")" << std::endl; std::this_thread::sleep_for (std::chrono::milliseconds (100 )); refCount.fetch_sub (1 ); } }; void multiThreadedExample () { auto resource = std::make_shared <SharedResource>("database" ); std::vector<std::thread> threads; for (int i = 0 ; i < 5 ; ++i) { threads.emplace_back ([resource]() { for (int j = 0 ; j < 3 ; ++j) { resource->use (); } }); } for (auto & thread : threads) { thread.join (); } }
动态内存管理的性能优化 内存分配策略 减少内存分配次数 :
一次性分配足够的内存 使用 std::vector 等容器管理动态数组 避免频繁的小内存分配 使用内存池 :
预先分配大块内存 从内存池中分配小块内存 减少内存碎片和分配开销 选择合适的智能指针 :
优先使用 std::unique_ptr(无额外开销) 仅在需要共享所有权时使用 std::shared_ptr(有引用计数开销) 使用 std::make_shared 减少内存分配次数 内存对齐 :
使用 alignas 关键字指定对齐要求 使用 std::aligned_alloc 分配对齐内存 提高内存访问速度 性能对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <chrono> void testRawPointer () { auto start = std::chrono::high_resolution_clock::now (); for (int i = 0 ; i < 1000000 ; i++) { int * p = new int (42 ); delete p; } auto end = std::chrono::high_resolution_clock::now (); std::chrono::duration<double > elapsed = end - start; std::cout << "Raw pointer: " << elapsed.count () << " seconds" << std::endl; } void testUniquePtr () { auto start = std::chrono::high_resolution_clock::now (); for (int i = 0 ; i < 1000000 ; i++) { auto p = std::make_unique <int >(42 ); } auto end = std::chrono::high_resolution_clock::now (); std::chrono::duration<double > elapsed = end - start; std::cout << "Unique ptr: " << elapsed.count () << " seconds" << std::endl; } void testSharedPtr () { auto start = std::chrono::high_resolution_clock::now (); for (int i = 0 ; i < 1000000 ; i++) { auto p = std::make_shared <int >(42 ); } auto end = std::chrono::high_resolution_clock::now (); std::chrono::duration<double > elapsed = end - start; std::cout << "Shared ptr: " << elapsed.count () << " seconds" << std::endl; } void testVector () { auto start = std::chrono::high_resolution_clock::now (); std::vector<int > v; v.reserve (1000000 ); for (int i = 0 ; i < 1000000 ; i++) { v.push_back (42 ); } auto end = std::chrono::high_resolution_clock::now (); std::chrono::duration<double > elapsed = end - start; std::cout << "Vector: " << elapsed.count () << " seconds" << std::endl; } int main () { testRawPointer (); testUniquePtr (); testSharedPtr (); testVector (); return 0 ; }
动态内存管理的最佳实践 优先使用自动存储 :
使用智能指针 :
优先使用 std::unique_ptr 仅在需要共享所有权时使用 std::shared_ptr 使用 std::make_unique 和 std::make_shared 使用标准容器 :
使用 std::vector 管理动态数组 使用 std::string 管理字符串 避免手动内存管理 内存安全 :
始终初始化指针 释放内存后将指针设置为 nullptr 使用智能指针避免内存泄漏 性能考虑 :
减少内存分配次数 使用内存池管理频繁分配的内存 选择合适的内存分配策略 异常安全 :
使用 RAII 原则管理资源 确保即使发生异常也能释放内存 使用智能指针提高异常安全性 现代C++中的动态内存管理 移动语义 :
使用移动构造函数和移动赋值运算符 减少内存复制,提高性能 支持 std::unique_ptr 的所有权转移 右值引用 :
允许高效移动资源 避免不必要的内存分配 支持 std::move 语义 类型擦除 :
使用 std::any 存储任意类型 使用 std::variant 存储有限类型集合 减少不必要的多态开销 内存资源 :
C++17 引入 std::pmr 命名空间 支持自定义内存资源 提高内存分配的灵活性和可预测性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <memory_resource> void usePolymorphicAllocator () { std::pmr::vector<int > v1; char buffer[1024 ]; std::pmr::monotonic_buffer_resource pool (buffer, sizeof (buffer)) ; std::pmr::vector<int > v2 (&pool) ; for (int i = 0 ; i < 100 ; i++) { v2. push_back (i); } }
命名空间 命名空间的定义 命名空间是C++中用于组织代码、避免名称冲突的机制,支持嵌套、内联和匿名等多种形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 namespace MyNamespace { int value = 100 ; void function () { std::cout << "MyNamespace::function()" << std::endl; } } namespace Outer { int outerValue = 200 ; namespace Inner { int innerValue = 300 ; void innerFunction () { std::cout << "Outer::Inner::innerFunction()" << std::endl; } } } inline namespace InlineNS { int inlineValue = 400 ; void inlineFunction () { std::cout << "InlineNS::inlineFunction()" << std::endl; } } namespace { int anonymousValue = 500 ; void anonymousFunction () { std::cout << "anonymousFunction()" << std::endl; } } namespace MyLibrary { namespace V1 { void process () { std::cout << "MyLibrary::V1::process()" << std::endl; } } namespace V2 { void process () { std::cout << "MyLibrary::V2::process()" << std::endl; } } using namespace V2; }
命名空间的使用 作用域解析运算符 使用作用域解析运算符 :: 是访问命名空间成员最明确、最安全的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 std::cout << MyNamespace::value << std::endl; MyNamespace::function (); std::cout << Outer::Inner::innerValue << std::endl; Outer::Inner::innerFunction (); std::cout << inlineValue << std::endl; inlineFunction ();std::cout << InlineNS::inlineValue << std::endl; InlineNS::inlineFunction (); std::cout << anonymousValue << std::endl; anonymousFunction ();MyLibrary::process (); MyLibrary::V1::process (); MyLibrary::V2::process ();
using 声明 using 声明将特定的命名空间成员引入当前作用域,减少了代码的冗长性,同时保持了一定的明确性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using MyNamespace::value;using MyNamespace::function;using Outer::Inner::innerValue;using Outer::Inner::innerFunction;using InlineNS::inlineValue;using InlineNS::inlineFunction;using MyLibrary::process;std::cout << value << std::endl; function ();std::cout << innerValue << std::endl; innerFunction ();std::cout << inlineValue << std::endl; inlineFunction ();process ();
using 指令 using 指令将整个命名空间的所有成员引入当前作用域,使用时需谨慎,避免名称冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using namespace MyNamespace;std::cout << value << std::endl; function ();using namespace Outer;using namespace std; cout << outerValue << endl; cout << Inner::innerValue << endl; Inner::innerFunction ();
命名空间的别名 命名空间别名可以简化长命名空间的使用,提高代码的可读性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 namespace MN = MyNamespace;namespace OI = Outer::Inner;namespace ML = MyLibrary;namespace MLV1 = MyLibrary::V1;std::cout << MN::value << std::endl; MN::function (); std::cout << OI::innerValue << std::endl; OI::innerFunction (); ML::process (); MLV1::process (); namespace MyLibV2 = MyLibrary::V2;MyLibV2::process ();
标准命名空间 标准库中的所有组件都位于 std 命名空间中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 std::cout << "Hello, world!" << std::endl; std::vector<int > vec = {1 , 2 , 3 }; std::string str = "C++" ; using std::cout;using std::endl;using std::vector;using std::string;cout << "Hello, world!" << endl; vector<int > numbers = {4 , 5 , 6 }; string message = "Modern C++" ; using namespace std;cout << "Hello, world!" << endl; vector<double > values = {1.1 , 2.2 , 3.3 }; string text = "C++20" ;
注意 :在头文件中应避免使用 using namespace std;,以防止命名冲突。
命名空间的高级特性 内联命名空间 内联命名空间(C++11+)允许直接访问其成员,无需通过命名空间名限定,主要用于版本管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 namespace Library { namespace V1 { void process () { std::cout << "Library::V1::process()" << std::endl; } } inline namespace V2 { void process () { std::cout << "Library::V2::process()" << std::endl; } void newFeature () { std::cout << "Library::V2::newFeature()" << std::endl; } } } Library::process (); Library::newFeature (); Library::V1::process ();
命名空间的 ADL(参数依赖查找) ADL 是 C++ 中的一种名称查找规则,允许在函数调用时自动查找与参数类型相关的命名空间中的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 namespace Math { class Vector { public : int x, y; Vector (int x, int y) : x (x), y (y) {} }; void print (const Vector& v) { std::cout << "Math::print(Vector): (" << v.x << ", " << v.y << ")" << std::endl; } } Math::Vector v (10 , 20 ) ;print (v); int x = 10 ;print (x);
命名空间与模板 命名空间可以与模板结合使用,组织模板代码并避免名称冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 namespace Utils { template <typename T> T max (T a, T b) { return a > b ? a : b; } template <typename T> class Stack { private : std::vector<T> elements; public : void push (const T& item) { elements.push_back (item); } void pop () { if (!elements.empty ()) { elements.pop_back (); } } T top () const { if (!elements.empty ()) { return elements.back (); } throw std::out_of_range ("Stack is empty" ); } bool empty () const { return elements.empty (); } }; } int a = 10 , b = 20 ;std::cout << Utils::max (a, b) << std::endl; Utils::Stack<int > stack; stack.push (1 ); stack.push (2 ); stack.push (3 ); std::cout << stack.top () << std::endl; stack.pop (); std::cout << stack.top () << std::endl;
名称查找 名称查找规则 C++中的名称查找遵循以下规则:
局部作用域 :首先在当前作用域查找外层作用域 :如果局部作用域没有找到,在外层作用域查找命名空间作用域 :如果外层作用域没有找到,在使用的命名空间中查找全局作用域 :如果命名空间作用域没有找到,在全局作用域查找:: 作用域 :如果指定了作用域解析运算符,则在指定的作用域中查找名称隐藏 当内层作用域中存在与外层作用域同名的标识符时,内层作用域的标识符会隐藏外层作用域的标识符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int x = 100 ; void function () { int x = 200 ; std::cout << x << std::endl; std::cout << ::x << std::endl; { int x = 300 ; std::cout << x << std::endl; std::cout << ::x << std::endl; } std::cout << x << std::endl; } int main () { function (); return 0 ; }
命名空间的名称查找 在命名空间中,名称查找遵循同样的规则,内层命名空间的标识符会隐藏外层命名空间的标识符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 namespace A { int x = 10 ; void function () { std::cout << x << std::endl; } } namespace B { int x = 20 ; void function () { using namespace A; std::cout << x << std::endl; std::cout << A::x << std::endl; } namespace C { int x = 30 ; void function () { std::cout << x << std::endl; std::cout << B::x << std::endl; std::cout << A::x << std::endl; } } } int main () { B::function (); B::C::function (); return 0 ; }
依赖名称查找 在模板中,依赖于模板参数的名称查找在模板实例化时进行,这被称为依赖名称查找(ADL)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 namespace N { class X { }; void f (X) { std::cout << "N::f(X)" << std::endl; } } template <typename T>void g (T t) { f (t); } int main () { N::X x; g (x); return 0 ; }
内存模型和名称空间的最佳实践 内存管理最佳实践 优先使用自动存储 :对于局部变量,优先使用自动存储(栈内存),减少动态内存分配使用智能指针 :对于动态内存,优先使用智能指针(std::unique_ptr、std::shared_ptr),避免内存泄漏避免手动内存管理 :尽量避免使用 new 和 delete 手动管理内存,使用标准容器和智能指针内存分配检查 :在分配内存后检查是否成功(对于 new(nothrow)),确保内存分配失败时能够优雅处理释放内存 :确保每个 new 都有对应的 delete,每个 new[] 都有对应的 delete[],避免内存泄漏避免悬空指针 :释放内存后将指针设置为 nullptr,避免悬空指针导致的未定义行为避免重复释放 :不要重复释放同一块内存,释放后设置为 nullptr 可以避免这种情况内存对齐 :对于性能敏感的代码,考虑内存对齐,提高内存访问速度内存池 :对于频繁分配和释放的小块内存,使用内存池减少内存碎片RAII 原则 :使用 RAII 原则管理资源,确保资源在作用域结束时自动释放命名空间最佳实践 合理使用命名空间 :使用命名空间组织代码,避免名称冲突,提高代码的可读性和可维护性避免 using 指令的滥用 :在头文件中避免使用 using namespace std;,防止命名冲突影响其他代码使用 using 声明 :对于常用的标识符,使用 using 声明简化代码,如 using std::cout;命名空间命名 :使用有意义的命名空间名称,遵循项目的命名约定,避免使用缩写(除非是广泛认可的)嵌套命名空间 :合理使用嵌套命名空间组织代码,避免过深的嵌套(一般不超过3层)匿名命名空间 :对于只在当前文件中使用的内容,使用匿名命名空间,替代静态全局变量和函数内联命名空间 :使用内联命名空间进行版本管理,提供向后兼容性命名空间别名 :使用命名空间别名简化长命名空间的使用,提高代码的可读性版本化命名空间 :使用版本化命名空间管理 API 演进,确保向后兼容性模块系统 :在 C++20 及以上版本中,考虑使用模块系统替代传统的头文件包含机制C++20新特性:模块系统 C++20引入了模块系统,提供了一种更高效、更可靠的代码组织方式,替代传统的头文件包含机制:
模块的基本概念 模块 :一个独立的代码单元,包含声明和定义导出 :将模块中的符号暴露给其他模块使用导入 :使用其他模块导出的符号模块接口文件 :包含模块声明和导出语句的文件(.cppm 扩展名)模块实现文件 :包含模块实现的文件(.cpp 扩展名)模块的使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 module ; #include <cmath> export module math; export namespace math { double add (double a, double b) { return a + b; } double subtract (double a, double b) { return a - b; } double multiply (double a, double b) { return a * b; } double divide (double a, double b) { if (b == 0 ) { throw std::invalid_argument ("Division by zero" ); } return a / b; } double sqrt (double x) { return std::sqrt (x); } } import math;import std.core;int main () { using namespace math; std::cout << "2 + 3 = " << add (2 , 3 ) << std::endl; std::cout << "5 - 2 = " << subtract (5 , 2 ) << std::endl; std::cout << "3 * 4 = " << multiply (3 , 4 ) << std::endl; std::cout << "10 / 2 = " << divide (10 , 2 ) << std::endl; std::cout << "sqrt(16) = " << sqrt (16 ) << std::endl; return 0 ; }
模块的优势 编译速度 :模块只需要编译一次,减少了重复编译的时间依赖管理 :模块明确声明依赖关系,避免了头文件的循环包含名称隔离 :模块提供了更好的名称隔离,减少了命名冲突接口清晰 :模块明确导出接口,隐藏实现细节安全性 :模块避免了宏的污染,提高了代码的安全性可维护性 :模块提供了更好的代码组织方式,提高了代码的可维护性模块与命名空间的关系 命名空间 :逻辑组织,避免名称冲突模块 :物理组织,提高编译速度,减少依赖模块可以包含多个命名空间,命名空间也可以跨多个模块使用。模块系统是命名空间的补充,提供了更强大的代码组织能力。
模块的未来发展 C++20 模块系统是一个重要的新特性,未来可能会有以下发展:
更广泛的编译器支持 :所有主流编译器都将支持模块系统标准库模块化 :标准库将被重构为模块,提高编译速度包管理器集成 :模块系统将与包管理器集成,简化依赖管理工具链支持 :IDE 和构建工具将提供更好的模块支持模块化最佳实践 :将形成一套模块化的最佳实践和设计模式总结 内存模型和命名空间是C++中两个重要的概念,它们共同构成了C++程序的基础架构:
内存模型的关键点 内存区域 :代码段、数据段、BSS段、堆、栈内存管理 :静态内存分配、动态内存分配、智能指针内存安全 :避免内存泄漏、悬空指针、重复释放内存优化 :内存对齐、内存池、缓存利用命名空间的关键点 代码组织 :使用命名空间组织代码,避免名称冲突命名空间特性 :嵌套命名空间、内联命名空间、匿名命名空间命名空间使用 :作用域解析运算符、using 声明、using 指令模块系统 :C++20 模块系统提供了更高效的代码组织方式最佳实践 内存管理 :优先使用自动存储和智能指针,避免手动内存管理命名空间 :合理使用命名空间组织代码,避免 using 指令的滥用代码组织 :使用模块化的设计思想,提高代码的可读性和可维护性性能优化 :考虑内存对齐、缓存利用等性能因素安全性 :遵循 RAII 原则,确保资源的正确管理通过深入理解和合理使用内存模型和命名空间,开发者可以编写更高效、更可靠、更可维护的C++代码,充分发挥C++的优势。
export double add(double a, double b) { return a + b; }
export double square(double x) { return x * x; }
export double sqrt(double x) { return std::sqrt(x); }
// main.cpp - 使用模块 import math; // 导入math模块 import std; // 导入标准库模块(C++23)
int main() { double a = 3.0; double b = 4.0;
double sum = add(a, b);
double squared = square(sum);
double root = sqrt(squared);
std::cout << "Sum: " << sum << std::endl;
std::cout << "Squared: " << squared << std::endl;
std::cout << "Square root: " << root << std::endl;
return 0;
}
### 模块的优点
1. **编译速度**:模块只编译一次,避免了头文件的重复包含和编译
2. **依赖管理**:明确的导入/导出机制,避免了隐式依赖
3. **名称冲突**:模块提供了命名空间的隔离,减少了名称冲突
4. **安全性**:模块的接口更加清晰,减少了错误的使用方式
5. **可维护性**:代码组织更加模块化,易于维护和理解
## 常见错误和陷阱
### 内存管理错误
1. **内存泄漏**:忘记释放动态分配的内存
2. **悬空指针**:使用已释放的内存
3. **重复释放**:多次释放同一块内存
4. **数组和单个对象释放混淆**:使用错误的 delete 形式
5. **内存碎片**:频繁分配和释放小块内存导致内存碎片
### 命名空间错误
1. **命名空间冲突**:不同命名空间中的名称冲突
2. **using 指令的滥用**:在头文件中使用 using 指令导致的命名冲突
3. **名称查找歧义**:多个命名空间中存在相同名称的标识符
4. **命名空间嵌套过深**:过度嵌套命名空间导致代码可读性下降
### 作用域错误
1. **变量隐藏**:局部变量隐藏全局变量
2. **作用域混淆**:误解变量的作用域范围
3. **生命周期错误**:使用已销毁的对象
## 小结
本章介绍了C++的内存模型和名称空间,包括:
1. **内存模型**:内存区域划分、内存布局
2. **存储类别**:auto、static、extern、register
3. **作用域**:全局作用域、局部作用域、块作用域、函数原型作用域、类作用域、命名空间作用域
4. **链接性**:外部链接性、内部链接性、无链接性
5. **动态内存管理**:new/delete、智能指针(unique_ptr、shared_ptr、weak_ptr)
6. **名称空间**:命名空间的定义、使用、别名
7. **名称查找**:名称查找规则、名称隐藏
8. **最佳实践**:内存管理和命名空间的使用建议
9. **常见错误和陷阱**:内存管理错误、命名空间错误、作用域错误
理解C++的内存模型和名称空间对于编写高效、可靠的程序至关重要。通过合理管理内存和使用命名空间,可以提高代码的可读性、可维护性和安全性。在实际编程中,应优先使用智能指针管理动态内存,避免手动内存管理的错误;合理使用命名空间组织代码,避免名称冲突。
在后续章节中,我们将学习面向对象编程、类和对象、继承和多态等更高级的C++特性,这些特性将与内存模型和名称空间结合使用,帮助我们构建更复杂、更强大的程序。