Effective C++ 条款20:宁以pass-by-reference-to-const替换pass-by-value
Effective C++ 条款20:宁以pass-by-reference-to-const替换pass-by-value
核心思想:优先使用const T&
传递参数代替值传递,避免昂贵的拷贝开销并防止对象切割问题,同时保持参数不可修改的安全性。
⚠️ 1. 值传递的代价与陷阱
昂贵拷贝开销:
class BigObject {
public:BigObject(); // 构造耗时BigObject(const BigObject&); // 拷贝昂贵(含10个vector成员)~BigObject();
private:std::vector<double> data1, data2, ...;
};// 值传递:触发完整拷贝
void process(BigObject bo); // 每次调用拷贝构造+析构process(BigObject()); // 拷贝成本:O(n) * 10
对象切割问题:
class Window {
public:virtual void display() const; // 基类虚函数
};class SpecialWindow : public Window {
public:void display() const override; // 派生类实现
};// 值传递导致派生类被切割
void showWindow(Window w) { // 参数类型为基类w.display(); // 总是调用Window::display()
}SpecialWindow sw;
showWindow(sw); // 传入SpecialWindow → 被切割为Window对象
🚨 2. 解决方案:const引用传递
高效传递大对象:
// 引用传递:零拷贝开销
void process(const BigObject& bo); // 仅传递指针大小的引用BigObject obj;
process(obj); // 无拷贝构造/析构调用
保持多态完整性:
// 引用传递保持对象完整类型
void showWindow(const Window& w) { // 参数为基类引用w.display(); // 动态绑定正确调用派生类方法
}SpecialWindow sw;
showWindow(sw); // 调用SpecialWindow::display()
⚖️ 3. 关键原则与例外情况
传递方式 | 适用场景 | 性能影响 |
---|---|---|
const T& | 自定义类型、类层次结构中的对象 | 零拷贝开销 |
值传递 | 内置类型、小型POD结构 | 拷贝成本可忽略 |
移动语义(T&&) | 需要转移所有权的可修改对象 | 转移资源所有权 |
智能指针 | 需要共享所有权的对象 | 引用计数操作 |
内置类型值传递例外:
// 内置类型直接传值更高效
void validate(int x, float y); // 比const int&更高效// 小型POD结构(<16字节)
struct Point2D { float x, y; };
void translate(Point2D p); // 寄存器传递可能更快
STL迭代器和函数对象:
// 迭代器设计为值传递
template<typename InputIt>
void processRange(InputIt begin, InputIt end); // 按值传递迭代器// 函数对象可能按值传递
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // lambda值传递
现代C++扩展规则:
// 通用引用模板(C++11)
template<typename T>
void forwardExample(T&& arg); // 完美转发参数// 移动语义优化
void sinkExample(BigObject&& bo); // 明确转移所有权
💡 关键原则总结
- 默认传递策略
- 自定义类型 →
const T&
- 内置类型 → 直接传值
- 自定义类型 →
- 多态保护原则
- 基类类型参数必须使用引用传递
- 避免对象切割(slicing problem)
- 性能临界区优化
- 小型结构体实测性能对比
- 高频调用场景考虑内联
- 现代C++增强
- 所有权转移使用
T&&
- 完美转发使用
T&& + std::forward
- 所有权转移使用
值传递陷阱重现:
class Employee { public:virtual int calculateSalary() const; };class Manager : public Employee {int calculateSalary() const override; // 更高计算复杂度 };// 危险:值传递导致切割+性能双问题 void processEmployee(Employee e) {int salary = e.calculateSalary(); // 1. 调用基类方法 } // 2. 触发完整拷贝Manager m; processEmployee(m); // 对象被切割,额外拷贝基类部分
安全重构方案:
// 解决方案1:const引用传递(推荐) void processEmployee(const Employee& e) {int salary = e.calculateSalary(); // 动态绑定正确 } // 无拷贝开销// 解决方案2:智能指针传递(共享所有权) void processEmployee(std::shared_ptr<const Employee> e);// 解决方案3:移动语义(转移所有权) void takeOwnershipEmployee(Employee&& e);// 解决方案4:模板泛型(C++17) template<typename T, typename = std::enable_if_t<std::is_base_of_v<Employee, T>>> void processTemplate(const T& emp);