“模仿别人是必要的,但重复自己是可悲的。” ——巴勃罗·毕加索

一个名字,没有复制;两个名字,两个副本

在任何作用域内(包括触发RVO的情况),要知道发生了多少次复制,只需要检查你的数据有多少个名字。

在任意时刻,如果两份数据有两个有效的名字,那这些数据就有两个副本。作为一个很好的近似,

在除此以外的情况下,编译器会(而且尝尝是必须)避免复制。

因此,如果你的代码中,一份数据在某个时间点有两个名字,那么你应该期待有一次复制。如果你避免了指向一份数据的多余的名字,那么你在帮助编译器移除不必要的复制。

示例

通过几个例子,我们来看看实践中是怎么使用这个定则的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::string build();

std::string foo(std::string arg) {
return arg; // 没有复制,数据`arg`只有一个名字。
}

void bar() {
std::string local = build(); // 只有一个实例,只有一个名字

// 没有复制,引用不会触发复制
std::string& local_ref = local;

// 一次复制操作,现在同样的数据(`local`中的内容,译者注)有了两个名字。
std::string second = foo(local);
}

大部分情况下,这些都不重要。相比于担心复制和效率,代码的可读性和一致性重要的多。一如既往的:优化之前先分析。(这也是Google内部的一个尝试,因此成为一如既往的。也可以理解为“不要瞎优化”。)当然,如果你正在从头开始写信的代码——而且能够提供干净和一致的API来返回值——不要为“可能”的复制而破坏了你的API:对于复制,你所学到的十年前的所有只是都是错的。