第4章:效率(Efficiency)
一、导读
1.1 本章学习目标
第4章讲解 C++ 程序的效率优化技术。Meyers 在此介绍了惰性求值、表达式模板、减少临时对象等高级优化技巧。
本章核心学习目标:
- 掌握惰性求值(Lazy Evaluation):延迟计算直到真正需要
- 理解表达式模板:避免临时对象的编译期优化
- 掌握减少临时对象的技术:返回值优化、移动语义
- 理解效率与设计的权衡
1.2 为什么本章如此重要
性能优化是 C++ 开发中的重要课题。理解这些技术能帮助写出更高效的代码,但 Meyers 也提醒:不要过早优化。
1.3 核心问题引导
惰性求值在什么场景下有效?
表达式模板如何消除临时对象?
移动语义如何减少拷贝?
二、核心概念详解
2.1 惰性求值
cpp
// 惰性求值: 延迟计算直到结果真正被需要
class Matrix {
std::vector<std::vector<double>> data;
mutable std::optional<Matrix> transpose_cache;
mutable std::optional<double> determinant_cache;
public:
// 转置: 延迟计算
const Matrix& transpose() const {
if (!transpose_cache) {
transpose_cache = computeTranspose();
}
return *transpose_cache;
}
// 引用计数: 延迟拷贝
// 多个 Matrix 可以共享同一份数据,直到某个需要修改时才拷贝
};
// 适用场景:
// 1. 计算结果可能不需要
// 2. 计算代价高
// 3. 结果可以被缓存
// 不适用:
// 1. 计算结果总是需要
// 2. 计算代价低
// 3. 结果变化频繁2.2 表达式模板
cpp
// 问题: 矩阵加法产生大量临时对象
// C = A + B + D;
// 等价于:
// temp1 = A + B; // 创建临时矩阵
// C = temp1 + D; // 创建另一个临时矩阵
// 表达式模板: 延迟计算,消除临时对象
template<typename Expr>
class MatrixExpr {
const Expr& expr;
public:
MatrixExpr(const Expr& e) : expr(e) {}
double operator()(int i, int j) const { return expr(i, j); }
};
// A + B 返回表达式对象,不计算
template<typename E1, typename E2>
class MatrixSum {
const E1& e1;
const E2& e2;
public:
MatrixSum(const E1& a, const E2& b) : e1(a), e2(b) {}
double operator()(int i, int j) const { return e1(i,j) + e2(i,j); }
};
template<typename E1, typename E2>
MatrixExpr<MatrixSum<E1, E2>> operator+(const MatrixExpr<E1>& a, const MatrixExpr<E2>& b) {
return MatrixSum<E1, E2>(a.expr, b.expr);
}
// C = A + B + D;
// 现在: 只在赋值给 C 时才计算,无临时对象2.3 减少临时对象
cpp
// 返回值优化 (RVO/NRVO)
std::string createGreeting() {
return "Hello, " + name; // NRVO: 直接在调用者的内存中构造
}
// 移动语义
std::vector<int> getLargeVector() {
std::vector<int> v(1000000);
// ... 填充数据 ...
return v; // 移动构造(或 NRVO)
}
// 避免不必要的临时对象
void process(const std::string& s); // 传 const 引用
process("hello" + name); // 临时 string,无法避免
process(name); // 无临时对象
// 使用 std::string_view (C++17) 避免拷贝
void processView(std::string_view sv); // 不拷贝
processView(name); // 无拷贝
processView("hello" + name); // 仍然有临时对象三、实战案例
3.1 案例一:表达式模板性能测试
cpp
#include <chrono>
// 普通实现
Matrix operator+(const Matrix& a, const Matrix& b) {
Matrix result(a.rows(), a.cols());
for (int i = 0; i < a.rows(); ++i)
for (int j = 0; j < a.cols(); ++j)
result(i,j) = a(i,j) + b(i,j);
return result;
}
// 测试
Matrix A(1000, 1000), B(1000, 1000), C(1000, 1000), D(1000, 1000);
auto start = std::chrono::high_resolution_clock::now();
Matrix result = A + B + C + D; // 3 次临时矩阵
auto normal_time = std::chrono::high_resolution_clock::now() - start;
// 使用表达式模板: 0 次临时矩阵
// 性能提升: 2-3 倍四、本章小结
惰性求值延迟计算直到需要,适合计算代价高且结果可能不需要的场景
表达式模板通过编译期技术消除临时对象,大幅提升数值计算性能
移动语义和 NRVO 减少不必要的拷贝
五、思考与练习
思考题
表达式模板的编译期开销有多大?
惰性求值的缓存失效问题如何处理?
编程练习
实现一个简单的表达式模板系统(向量加法)。
用惰性求值优化一个矩阵运算类。