C++异常处理与命名空间详解
立即解锁
发布时间: 2025-08-16 01:20:54 阅读量: 16 订阅数: 73 


C++编程语言第四版:核心概念与技术
# C++ 异常处理与命名空间详解
## 1. 向量操作与异常处理
### 1.1 向量操作特性
向量操作时,其值保持不变,空间也不会自动增加。不过,`reserve()` 函数可能已经对现有元素进行了重新分配。`push_back()` 函数的定义中包含两个“魔法数字”(2 和 8)。在实际工业级实现中,不会直接使用这样的数字,但仍然会有确定初始分配大小(这里是 8)和增长速率(这里是 2,表示每次向量溢出时大小翻倍)的值。这些值并非不合理或不常见,其假设是一旦对向量使用了一次 `push_back()`,之后很可能会多次使用。因子 2 比数学上最小化平均内存使用的最优因子(1.618)大,这样能为内存不是极小的系统提供更好的运行时性能。
### 1.2 异常处理策略
在向量实现中,除了 `uninitialized_copy()` 内部隐藏的一个 `try` 块外,没有其他 `try` 块。通过精心安排操作顺序来实现状态更改,确保如果抛出异常,向量保持不变或至少处于有效状态。通过顺序和 RAII 技术(资源获取即初始化)来实现异常安全,通常比使用 `try` 块显式处理错误更优雅、更高效。异常安全问题更多是由于程序员对代码的不合理排序,而非缺乏特定的异常处理代码。排序的基本规则是在替换信息构建完成且能无异常赋值之前,不要销毁原信息。
### 1.3 异常处理建议
为了更好地进行异常处理,以下是一些建议:
1. **早期制定策略**:在设计初期就制定错误处理策略。
2. **抛出异常**:当无法完成分配的任务时,抛出异常。
3. **使用异常处理错误**:将异常用于错误处理。
4. **自定义异常类型**:使用专门设计的用户自定义类型作为异常,而非内置类型。
5. **模拟异常**:若因某些原因无法使用异常,可模拟异常处理。
6. **分层处理**:采用分层错误处理。
7. **简化处理部分**:保持错误处理的各个部分简单。
8. **避免全捕获**:不要试图在每个函数中捕获所有异常。
9. **提供基本保证**:始终提供基本保证。
10. **提供强保证**:除非有理由不这样做,否则提供强保证。
11. **构造函数抛出异常**:让构造函数建立不变量,若无法建立则抛出异常。
12. **释放资源**:在抛出异常前释放本地拥有的资源。
13. **确保资源释放**:确保在构造函数中获取的每个资源在抛出异常时都能释放。
14. **避免过度使用异常**:在局部控制结构足以处理时,不使用异常。
15. **使用 RAII 技术**:使用“资源获取即初始化”技术管理资源。
16. **减少 try 块使用**:尽量减少 `try` 块的使用。
17. **并非所有程序都需异常安全**:并非每个程序都需要异常安全。
18. **维护不变量**:使用“资源获取即初始化”和异常处理程序维护不变量。
19. **优先使用资源句柄**:优先使用适当的资源句柄,而非结构较松散的 `finally`。
20. **围绕不变量设计策略**:围绕不变量设计错误处理策略。
21. **编译时检查**:能在编译时检查的内容,通常最好在编译时检查(使用 `static_assert`)。
22. **允许不同检查级别**:设计错误处理策略以允许不同级别的检查和执行。
23. **声明 noexcept**:若函数不会抛出异常,声明为 `noexcept`。
24. **避免异常规范**:不使用异常规范。
25. **引用捕获异常**:通过引用捕获可能属于层次结构的异常。
26. **不假设所有异常派生自 exception**:不要假设每个异常都派生自 `class exception`。
27. **main 函数捕获异常**:让 `main()` 函数捕获并报告所有异常。
28. **避免信息销毁**:在替换信息准备好之前,不要销毁原信息。
29. **保持操作数有效**:在赋值操作抛出异常前,让操作数处于有效状态。
30. **避免析构函数抛出异常**:永远不要让异常从析构函数中逃逸。
31. **分离代码**:将普通代码和错误处理代码分开。
32. **防止内存泄漏**:注意因 `new` 分配的内存在异常情况下未释放而导致的内存泄漏。
33. **假设异常会抛出**:假设函数可能抛出的每个异常都会被抛出。
34. **库不单方面终止程序**:库不应单方面终止程序,而应抛出异常让调用者决定。
35. **库不产生终端用户诊断输出**:库不应产生针对终端用户的诊断输出,而应抛出异常让调用者决定。
## 2. 命名空间
### 2.1 组合问题
任何实际的程序都由多个独立部分组成。函数和类提供了相对细粒度的关注点分离,而“库”、源文件和翻译单元则提供了更粗粒度的分离。理想的情况是模块化,即保持不同的事物分离,并仅通过明确定义的接口访问“模块”。然而,在实际编程中,当人们没有为模块化进行设计时,会出现一些问题。
例如,一个图形库可能提供不同类型的图形形状和相关函数:
```cpp
// Graph_lib:
class Shape { /* ... */ };
class Line : public Shape { /* ... */ };
class Poly_line: public Shape { /* ... */ };
// connected sequence of lines
class Text : public Shape { /* ... */ };
// text label
Shape operator+(const Shape&, const Shape&);
// compose
Graph_reader open(const char*);
// open file of Shapes
```
另一个库可能提供文本处理功能:
```cpp
// Text_lib:
class Glyph { /* ... */ };
class Word { /* ... */ };
// sequence of Glyphs
class Line { /* ... */ };
// sequence of Words
class Text { /* ... */ };
// sequence of Lines
File* open(const char*);
// open text file
Word operator+(const Line&, const Line&);
// concatenate
```
如果将这两个库的头文件包含在一个程序中:
```cpp
#include "Graph_lib.h"
#include "Text_lib.h"
// ...
```
会导致编译器无法消除歧义的错误消息,因为 `Line`、`Text` 和 `open()` 被定义了两次。使用这些库还会产生更多错误消息。
处理此类名称冲突有多种技术,如将库的所有功能放在几个类中、使用不常见的名称或为库中的名称系统地添加前缀。但这些技术并非通用,使用起来可能不方便,例如名称会变长,使用过多不同名称会抑制泛型编程。
### 2.2 命名空间的引入
命名空间的概念用于直接表示一组直接相关的功能,例如库的代码。命名
0
0
复制全文
相关推荐










