第5章:实现(Implementations)
一、导读
1.1 本章学习目标
第5章讲解 C++ 实现层面的最佳实践:延迟计算、内存布局、内联函数、pimpl 惯用法等。
本章核心学习目标:
- 掌握延迟计算(Lazy Evaluation):只在需要时才计算
- 理解内存布局的影响:struct padding、缓存友好
- 掌握 pimpl 惯用法:隐藏实现细节,减少编译依赖
- 理解内联函数的利弊
1.2 为什么本章如此重要
实现层面的决策直接影响代码的性能、可维护性和编译时间。
1.3 核心问题引导
延迟计算在什么场景下有效?有什么代价?
pimpl 惯用法如何减少编译依赖?
内联函数为什么不一定更快?
二、核心概念详解
2.1 延迟计算
cpp
// 延迟计算: 只在需要时才执行昂贵的计算
class LargeMatrix {
std::vector<std::vector<double>> data;
mutable std::optional<std::vector<std::vector<double>>> transpose_cache;
public:
const std::vector<std::vector<double>>& getTranspose() const {
if (!transpose_cache) {
transpose_cache = computeTranspose();
}
return *transpose_cache;
}
private:
std::vector<std::vector<double>> computeTranspose() const {
// 昂贵的转置计算
int rows = data.size(), cols = data[0].size();
std::vector<std::vector<double>> result(cols, std::vector<double>(rows));
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
result[j][i] = data[i][j];
return result;
}
};
// 适用场景: 计算结果可能不需要、计算代价高
// 不适用: 计算结果总是需要、计算代价低2.2 pimpl 惯用法
cpp
// 头文件 (Widget.h)
class Widget {
public:
Widget();
~Widget();
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
void doSomething();
private:
struct Impl;
std::unique_ptr<Impl> pimpl; // 指向实现的指针
};
// 实现文件 (Widget.cpp)
struct Widget::Impl {
std::string name;
std::vector<int> data;
// ... 大量实现细节 ...
void doSomething() {
// 实现
}
};
Widget::Widget() : pimpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;
void Widget::doSomething() {
pimpl->doSomething();
}
// 优势:
// 1. 隐藏实现细节(二进制兼容)
// 2. 减少编译依赖(修改 Impl 不需要重新编译使用 Widget 的代码)
// 3. 加快编译速度(头文件不包含 Impl 的成员)三、实战案例
3.1 案例一:内存布局优化
cpp
// 不好的内存布局(浪费空间)
struct BadStruct {
bool flag1; // 1 byte + 7 padding
double value; // 8 bytes
bool flag2; // 1 byte + 7 padding
int count; // 4 bytes + 4 padding
}; // 总计: 32 bytes
// 好的内存布局(紧凑排列)
struct GoodStruct {
double value; // 8 bytes
int count; // 4 bytes
bool flag1; // 1 byte
bool flag2; // 1 byte
// 2 bytes padding
}; // 总计: 16 bytes
static_assert(sizeof(BadStruct) == 32);
static_assert(sizeof(GoodStruct) == 16);四、本章小结
延迟计算避免不必要的计算,但增加复杂度
pimpl 惯用法隐藏实现细节,减少编译依赖
内存布局影响空间效率和缓存性能
五、思考与练习
思考题
pimpl 惯用法对性能有什么影响?
什么时候应该使用延迟计算?
编程练习
用 pimpl 惯用法重构一个现有的类。
优化一个结构体的内存布局,减少空间浪费。