为什么要重新审视'传值'?
传统上,要实现'左值拷贝、右值移动'有两种方式,但都有缺陷:
- 重载方案:写两个函数(const T& 处理左值、T&& 处理右值),代码冗余(两份声明/实现/维护),目标代码也会生成两份;
- 通用引用方案:用模板 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 次移动),需权衡'代码简洁性'和'极致性能'——若软件要求绝对最优性能,仍需用按引用方案;
- 仅对'可拷贝'形参:只可移动类型(如
std::unique_ptr)不适用 —— 传值会多 1 次移动(总 2 次),而重载只需 1 个接受右值引用的函数(总 1 次移动),更高效; - 仅对'移动成本低'的形参:若移动成本高(如大对象),额外 1 次移动的开销会抵消简洁性的好处;
- 仅对'总是被拷贝'的形参:若形参可能不被拷贝(如校验名字长度失败则不添加),传值的'构造 + 析构形参'开销会白白浪费(按引用可避免)。
额外复杂度:构造拷贝 vs 赋值拷贝
传值的开销分析还需区分'形参是通过构造拷贝'还是'通过赋值拷贝':
- 构造拷贝(如 addName):形参被构造后 move 进新容器,额外 1 次移动的开销可接受;
- :

