9Effective C++(第3版)

杂项讨论

其他主题

异常安全效率STLtr1/Boost

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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 算法重写一个手写循环的数据处理程序。


9/9杂项讨论
0%