临时对象在C++中是不可避免的吗?

Are temporary objects unavoidable in C++?

本文关键字:不可避免 C++ 临时对象      更新时间:2023-10-16

我们在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 循环的开销更大?

将"临时"一词改为"寄存器";)

通常,编译过程的第一步是编译器将规范化(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)

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

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

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