简体   繁体   中英

Are temporary objects unavoidable in C++?

We read about different instances in C++ where temporary objects are created in code. See for example . Now, consider the following snippet of code.

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]);
    }
}

Are temporaries created when evaluating the numerator and denominator in the RHS of the equation in the inner for loop? Obviously, one can evaluate the expression in parts and store the results in intermediate variables as below.

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;
    }
}

Doesn't the latter approach introduce additional computational steps and therefore more overhead for the inner for loop?

Replace the word 'temporary' with the word 'register' ;)

In general the first step in the compilation process is that the compiler will canonicalize ( https://en.wikipedia.org/wiki/Canonicalization ) the code so that it takes a standardised form. It doesn't really matter how you format your code, or whether use temps or not, the compiler will re-arrange the code in both cases to be more or less identical. Chances are, it will end up generating something along these lines for both versions of your code:


        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;

From there, the canonized form will be converted into assembly. In psuedo-code, this is roughly along the lines of the assembly that a compiler for x64 might generate:

        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)

Pretty much every compiler will attempt to minimise the number of loads and stores (for they can be very expensive - eg cache misses, false sharing, etc) , and the rest of the temps will be stored as registers (assuming you don't run out!) .

If you have a relatively complex object, then you'd probably want to ensure you avoiding making temporary copies of the object. Even then, so long as the copy-constructor doesn't have side effects outside of the class, there is a good chance the compiler will elide those copies (eg return value optimisation).

Basically it's not something you really need to worry about. The compiler will pretty much ignore you anyway, rearrange your code as it sees fit, and generate resulting assembly that is as good as it can.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM