首页 > 解决方案 > 使用引用进行高效交换?

问题描述

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

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

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

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

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

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

标签: c++

解决方案


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

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++ 编译器在生成机器代码方面非常糟糕,这些机器代码不是写的,而是写的时候想到的……


推荐阅读