首页 > 解决方案 > 临时对象在 C++ 中是不可避免的吗?

问题描述

我们阅读了 C++ 中在代码中创建临时对象的不同实例。参见例如。现在,考虑以下代码片段。

const int rows{250};
const int cols{250};
const double k{0.75};

double A[rows][cols];
double B[rows][cols];
double C[rows][cols];

// Code that initialises A and B
...

for (int i{}; i<rows; ++i) {
    for (int j{}; j<cols; ++j) {
        C[i][j] = k*A[i][j]*B[i][j]/(A[i][j]*A[i][j] + B[i][j]*B[i][j]);
    }
}

在计算内部 for 循环中方程的 RHS 中的分子和分母时是否创建了临时变量?显然,可以部分地评估表达式并将结果存储在中间变量中,如下所示。

for (int i{}; i<rows; ++i) {
    for (int j{}; j<cols; ++j) {
        double temp1 = A[i][j]*B[i][j];
        double temp2 = k*temp1;
        double temp3 = A[i][j]*A[i][j];
        double temp4 = B[i][j]*B[i][j];
        double temp5 = temp3 + temp4;
        C[i][j] = temp2/temp5;
    }
}

后一种方法是否不会引入额外的计算步骤,因此不会为内部 for 循环带来更多开销?

标签: c++c++17

解决方案


用“注册”一词替换“临时”一词;)

一般来说,编译过程的第一步是编译器将规范化(https://en.wikipedia.org/wiki/Canonicalization)代码,使其采用标准化形式。不管你如何格式化你的代码,或者是否使用 temps,编译器都会在这两种情况下重新排列代码,使其或多或少相同。很有可能,它最终会为您的代码的两个版本生成以下内容:


        double temp1 = A[i][j]*B[i][j];
        double temp2 = k*temp1;
        double temp3 = A[i][j]*A[i][j];
        double temp4 = B[i][j]*B[i][j];
        double temp5 = temp3 + temp4;
        C[i][j] = temp2/temp5;

从那里,规范化的形式将被转换为汇编。在伪代码中,这大致与 x64 编译器可能生成的程序集的行一致:

        xmm0 = load(B[i][j])
        xmm1 = load(A[i][j])
        xmm2 = xmm0 * xmm1;  // A[i][j]*B[i][j];
        xmm3 = load(k)
        xmm3 = xmm3 * xmm2;  // k*temp1;
        xmm1 = xmm1 * xmm1;  // A[i][j]*A[i][j];
        xmm0 = xmm0 * xmm0;  // B[i][j]*B[i][j];
        xmm0 = xmm1 + xmm0   // temp3 + temp4;
        xmm0 = xmm3 / xmm0   // temp2 / temp5;
        store(C[i][j], xmm0)

几乎每个编译器都会尝试最小化加载和存储的数量(因为它们可能非常昂贵 - 例如缓存未命中、错误共享等),其余的临时文件将存储为寄存器(假设你不运行出去!)

如果您有一个相对复杂的对象,那么您可能希望确保避免制作该对象的临时副本。即使那样,只要复制构造函数在类之外没有副作用,编译器很有可能会忽略这些副本(例如返回值优化)。

基本上这不是你真正需要担心的事情。无论如何,编译器几乎都会忽略您,重新排列您认为合适的代码,并生成尽可能好的结果程序集。


推荐阅读