【Effective Modern C++】第八章 微调:41. 对于移动成本低且总是被拷贝的可拷贝形参,考虑按值传递

为什么要重新审视 “传值”?

传统上,要实现 “左值拷贝、右值移动” 有两种方式,但都有缺陷:

  1. 重载方案:写两个函数(const T& 处理左值、T&& 处理右值),代码冗余(两份声明 / 实现 / 维护),目标代码也会生成两份;
  2. 通用引用方案:用模板 T&& + std::forward,虽只需一个函数,但模板需放头文件(膨胀)、支持类型多导致目标代码多、编译错误晦涩,还可能匹配意外类型。

能否用一个函数、不依赖通用引用,同时实现左值拷贝、右值移动? 可以 —— 用 “按值传递 + 移动语义”,但需明确适用条件。

传值 + 移动语义

1. 实现方式(以 addName 为例)
class Widget {
public:
    void addName(std::string newName) { // 形参按值传递
        names.push_back(std::move(newName)); // 函数内move进容器
    }
private:
    std::vector<std::string> names;
};
2. 原理
  • 左值(如已存在的 string 变量):形参newName拷贝构造,再move进容器 → 总开销:1 次拷贝 + 1 次移动;
  • 右值(如临时字符串):形参newName移动构造,再move进容器 → 总开销:2 次移动;
  • 对比 “按引用方案(重载 / 通用引用)”:按引用是 “左值 1 次拷贝、右值 1 次移动”,传值多了 1 次移动(但移动成本低,可接受)。
3. 优势
  • 源代码 / 目标代码都只有 1 个函数,无冗余、无模板复杂度;
  • 避免通用引用的头文件膨胀、编译错误晦涩等问题;
  • 效率接近按引用方案(仅多 1 次低成本的移动)。

传值的 4 个关键适用条件

  1. 仅 “考虑” 传值:传值开销略高(多 1 次移动),需权衡 “代码简洁性” 和 “极致性能”—— 若软件要求绝对最优性能,仍需用按引用方案;
  2. 仅对 “可拷贝” 形参:只可移动类型(如std::unique_ptr)不适用 —— 传值会多 1 次移动(总 2 次),而重载只需 1 个接受右值引用的函数(总 1 次移动),更高效;
  3. 仅对 “移动成本低” 的形参:若移动成本高(如大对象),额外 1 次移动的开销会抵消简洁性的好处;
  4. 仅对 “总是被拷贝” 的形参:若形参可能不被拷贝(如校验名字长度失败则不添加),传值的 “构造 + 析构形参” 开销会白白浪费(按引用可避免)。

额外复杂度:构造拷贝 vs 赋值拷贝

传值的开销分析还需区分 “形参是通过构造拷贝” 还是 “通过赋值拷贝”:

  1. 构造拷贝(如 addName):形参被构造后 move 进新容器,额外 1 次移动的开销可接受;

  2. 赋值拷贝(如 Password::changeTo)

    • 传值:左值实参→形参拷贝构造(分配新内存),再 move 赋值给成员(释放旧内存),可能触发两次动态内存操作;
    • 按引用:若成员内存足够(如旧 string 比新 string 长),可重用内存,避免内存分配 / 释放,开销远低于传值。

传值的固有风险:切片问题

传值仍保留 C++98 的经典问题 ——对象切片

  • 若形参是基类类型,传递派生类对象时,派生类的特征会被 “切掉”,仅保留基类部分;
  • 因此,基类类型的形参绝对不能按值传递(必须用引用 / 指针)。

调用链的累积开销

若调用链中多个函数都用传值,每个函数多 1 次移动,整体开销会累积,可能从 “可接受” 变成 “无法忍受”—— 而按引用传递无此问题。

总结

  1. 传值的适用场景:仅针对 “可拷贝、移动成本低、无条件被拷贝、非基类类型” 的形参,此时传值实现简单、代码 / 目标代码少,效率接近按引用方案;
  2. 传值的代价:比按引用多 1 次移动,赋值拷贝场景可能额外触发内存分配 / 释放,调用链累积会放大开销;
  3. 传值的风险:基类形参传值会导致对象切片,只可移动类型传值不如重载高效;
  4. 权衡:C++11 后传值不再 “绝对不可用”,但需在 “代码简洁性” 和 “极致性能” 之间取舍,仅在满足所有适用条件时考虑。

原著在线阅读地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值