第9章:杂项讨论(Miscellany)
一、导读
1.1 本章学习目标
第9章涵盖 Effective C++ 中其他重要的主题:异常安全、效率优化、STL 使用、tr1/Boost 等。
本章核心学习目标:
- 掌握异常安全的三个层次:基本保证、强保证、不抛保证
- 理解效率与设计的权衡
- 掌握 STL 的最佳实践
- 了解 tr1/Boost 的常用组件
1.2 为什么本章如此重要
异常安全、效率和 STL 使用是实际开发中经常遇到的问题。Meyers 的建议帮助读者在这些方面做出正确的决策。
1.3 核心问题引导
异常安全的三个层次有什么区别?
为什么不应该过早优化?
STL 容器的选择标准是什么?
二、核心概念详解
2.1 异常安全
cpp
// 异常安全的三个层次:
// 1. 基本保证 (Basic Guarantee):
// 异常发生后,对象处于有效状态,没有资源泄漏
class BasicSafe {
std::unique_ptr<Resource> res;
public:
void doWork() {
res->process(); // 如果抛出异常,res 自动释放
}
};
// 2. 强保证 (Strong Guarantee):
// 异常发生后,状态回滚到调用前(如同操作从未发生)
class StrongSafe {
int value;
public:
void increment() {
int old = value;
try {
value++;
mightThrow();
} catch (...) {
value = old; // 回滚
throw;
}
}
};
// 3. 不抛保证 (Nothrow Guarantee):
// 函数绝不抛出异常
class NothrowSafe {
int value;
public:
int getValue() const noexcept { return value; }
void swap(NothrowSafe& other) noexcept {
std::swap(value, other.value);
}
};
// copy-and-swap 实现强保证
class Widget {
std::unique_ptr<int[]> data;
size_t size;
public:
Widget& operator=(Widget other) noexcept { // 按值传递
swap(other); // noexcept 的 swap
return *this;
}
void swap(Widget& other) noexcept {
using std::swap;
swap(data, other.data);
swap(size, other.size);
}
};2.2 效率原则
cpp
// 不要过早优化,但要了解关键的性能陷阱
// 1. 避免不必要的拷贝
std::string getName() {
return "hello"; // NRVO 或移动语义,不拷贝
}
// 2. 延迟计算
class LazyValue {
mutable std::optional<int> cached;
int compute() const { /* 昂贵计算 */ return 42; }
public:
int get() const {
if (!cached) cached = compute();
return *cached;
}
};
// 3. 使用合适的容器
// vector: 随机访问 O(1),插入 O(n)
// list: 随机访问 O(n),插入 O(1)
// unordered_map: 查找 O(1) 平均
// map: 查找 O(log n),有序2.3 STL 最佳实践
cpp
// 1. 优先使用 emplace 而非 insert
std::vector<std::pair<int, std::string>> vec;
vec.push_back(std::make_pair(1, "hello")); // 构造 pair 再移动
vec.emplace_back(1, "hello"); // 原地构造,更高效
// 2. 使用算法而非手写循环
std::vector<int> nums = {3, 1, 4, 1, 5, 9};
// 不好:
int sum = 0;
for (int n : nums) sum += n;
// 好:
int sum2 = std::accumulate(nums.begin(), nums.end(), 0);
// 3. 了解容器的内存策略
std::vector<int> v;
v.reserve(1000); // 预分配,避免多次重新分配
v.shrink_to_fit(); // 释放多余内存三、实战案例
3.1 案例一:异常安全的文件操作
cpp
class FileTransaction {
std::string filename;
std::string backup;
bool committed = false;
public:
explicit FileTransaction(std::string fn)
: filename(std::move(fn)), backup(filename + ".bak") {
// 创建备份(强保证的第一步)
std::ifstream src(filename, std::ios::binary);
std::ofstream dst(backup, std::ios::binary);
dst << src.rdbuf();
}
void write(const std::string& content) {
std::ofstream file(filename);
if (!file) throw std::runtime_error("Cannot open file");
file << content;
// 如果这里抛出异常,备份仍然存在
}
void commit() noexcept {
// 删除备份,确认修改
std::remove(backup.c_str());
committed = true;
}
~FileTransaction() {
if (!committed) {
// 回滚: 恢复备份
std::ifstream src(backup, std::ios::binary);
std::ofstream dst(filename, std::ios::binary);
dst << src.rdbuf();
std::remove(backup.c_str());
}
}
};四、本章小结
异常安全分三个层次:基本保证、强保证、不抛保证
效率优化应在测量后进行,但要避免已知的性能陷阱
STL 提供了高效的容器和算法,应优先使用
五、思考与练习
思考题
为什么 swap 应该标记为 noexcept?
什么时候应该用 reserve 预分配 vector 的内存?
编程练习
实现一个异常安全的数据库事务类。
用 STL 算法重写一个手写循环的数据处理程序。