使用引用进行高效交换?

Efficient Swap Using References?

本文关键字:高效 交换 引用      更新时间:2023-10-16

我一直在阅读移动语义,我相信一个真正有效的交换如下(为了方便起见,我们现在将所有内容保留为 int 而不是使用泛型):

void swap(int& a, int& b) { 
int tmp = std::move(a);
a = std::move(b); 
b = std::move(tmp);
}

这是正确的交换实现(对于整数)对吧?第 2 行不会在 RAM 中为tmp创建额外的项目,对吧?

我的另一个问题是,使用引用而不是 std::move 的相同代码是否也像上面的 Move 语义示例一样出色(例如,不为tmp创建额外的变量)?

void swap(int& a, int& b) { 
int tmp = &a;
a = &b; 
b = &tmp;
}

上面的代码是否为tmp创建了一个额外的变量?它是否正确交换ab

内置类型没有移动构造函数或移动赋值运算符,因此您的第一个版本实际上与

void swap(int& a, int& b)
{ 
int tmp = a;
a = b; 
b = tmp;
}

您的第二个版本无法编译。但是,最有可能的是,您只是想写与我上面的版本相同的内容。两个版本都引入了一个名为tmp的临时变量,以保留需要分配给一个交换值的值,而另一个交换值已更改。

不要假设代码中的每个构造(例如变量)在生成的机器代码中只有一个相应的构造(例如"RAM 中的额外项"),它总是会导致。这不是现代编译器的工作方式。编译器的工作不是获取每一行源代码,并使用特定的机器指令序列执行 1:1 替换。编译器的工作是获取您用C++语言描述的整个程序,并生成一个等效的程序,例如机器代码,当执行时,其行为方式与C++源代码描述的程序的行为无法区分。

如果您查看生成的程序集,您会发现,在交换纯整数的情况下,没有理智的编译器会将此临时变量移动到内存,而是使用寄存器来交换值。您可以相信编译器知道在给定目标体系结构上交换两个整数的最有效方法是什么。如果你不能相信你的编译器知道这一点,那么你应该寻找一个更好的编译器......

实际上,仅仅通过单独查看它生成的代码,很难真正说明这个swap函数的"效率"有多大。在实践中,上述swap这样的功能通常应该完全优化。它通常会在机器代码中留下的唯一痕迹是它对数据流的影响(此处的示例),但永远不会有实际的调用指令来调用单独的机器代码来执行交换。在优化swap时,真正重要的事情是确保编译器实际上可以对其进行优化(这通常归结为确保无论在哪里使用它都是已知的定义)。

故事的寓意:不要专注于描述你认为机器代码应该如何工作,而是以一种你和编译器都可读的方式表达你的意图,首先,以一种基于语言规则而不是目标硬件规则正确描述预期行为的方式。永远不要依赖你认为某种语言在机器级别构建映射的内容。依靠C++语言对某种语言结构引起的行为的看法。现代C++编译器非常擅长将复杂的C++代码转换为非常精简和高效的机器代码,这些机器代码可以精确地执行C++中表达的内容。然而,他们通过积极利用这样一个事实来做到这一点,即所表达的行为也是唯一必须尊重的行为。因此,现代C++编译器非常不擅长生成机器代码,这些机器代码不是编写的内容,而只是在编写时思考......

使用引用进行交换将如下所示:

void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}

您只在声明它是引用时放置&,但在使用它时不会。引用可以像常规变量一样使用。

因为您使用的是 ints,所以此交换与使用std::move的交换一样高效。移动语义仅在使用拥有资源的类时提供好处。

例如,向量拥有一个指向数组的指针,因此移动向量比复制向量效率高得多,因为当您复制向量时,您必须复制向量内部数组中的所有数据。另一方面,如果您移动向量,则不必复制内部数组。它只需要将指针复制到数组,这要快得多。