3

I was wondering if it's possible for C++ compilers to optimize in a case like this. Assume we have a class like:

class Foo {
public:
  Foo() : a(10), b(11), c(12) {};
  int a;
  int b;
  int c;
};

And I use this class like:

int main(int argc, char *argv[]) {
  Foo f;
  f.a = 50;
  f.b = 51;
  f.c = 52;
  return 0;
}

Would the compiler generate code to set a, b, and c to their respective default values of 10, 11, 12, and then set them to 50, 51, 52? Or is it allowed to delay assigning those initial values and instead only ever assign the values later (50,51,52) since there is no read in between the writes? Basically will it have to generate code to write those six values, or can it optimize to three?

If so, does this apply also to more complex types (structs, classes)? What is it called and where can I read more about this?

If not, why not?

6
  • 1
    You can remove the constructor, provide default member initializers instead, and your class will become an aggregate. Then you can do aggregate initialization and not worry about it. Commented Dec 28, 2017 at 5:53
  • Unfortunately the class in question is not under my control -- Thanks for the tip though! Commented Dec 28, 2017 at 5:57
  • I would think that unless the compiler can check that every instantiation immediately discards the values that it would have to do the constructor as given. Commented Dec 28, 2017 at 5:59
  • As for you question, since the constructor,s definition is provided inline, I think it's likely modern compilers will do the optimization you want. It's perfectly valid under the as-if rule. No way to say for sure without looking an generated assembly, however. Commented Dec 28, 2017 at 6:03
  • I got curious and checked. Here's GCC and Clang's output. GCC optimizes aggressively from -O1, and Clang from -O2. Commented Dec 28, 2017 at 6:09

3 Answers 3

2

This obviously depends on the compiler--but there are certainly at least some compilers that can and will eliminate the dead stores. In fact, depending on how you use the results, the compiler may eliminate all stores, dead or otherwise.

For example, if we compile your code exactly as it is right now, we end up with assembly language like this:

xor eax, eax
ret

That's it--since you never use any of the values you store, it eliminates all the code dealing with those values entirely. All that's left is the fact that main returns 0, so it just generates code for main to return zero.

That's probably not a case you care a whole lot about though, so let's expand the code a bit, to show something closer to what you probably care about.

#include <iostream>

class Foo {
public:
  Foo() : a(10), b(11), c(12) {};
  int a;
  int b;
  int c;

  friend std::ostream &operator<<(std::ostream &os, Foo const &f) {
      return os << "(" << f.a << ", " << f.b << ", " << f.c << ")";
  }
};

int main() {
  Foo f;
  f.a = 50;
  f.b = 51;
  f.c = 52;

  std::cout << f << "\n";
}

In this case, the compiler still eliminates all the storage involved, and produces code to just directly write out the values we gave as literals in the source:

mov esi, 50
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)

[and the same sequence repeated for 51 and 52].

Reference:

Godbolt

Sign up to request clarification or add additional context in comments.

Comments

1

Yes, the compiler can optimize to remove the initialization. This technique is called dead store optimization. Determining whether a dead store occurs is part of data-flow analysis.

Comments

-2

It will first assign the three values to a,b and c and then after that change them to the later assigned values.

This is because in c++, execution is line by line. So first the object gets declared and it gets values assigned by the constructor as soon as declared. After that a,b and c values are changed.

To optimize it, you can use the constructor with default arguments like this

class Foo{
public:
int a, b, c;
Foo(x = 10, y = 11, z = 12){
   a = x;
   b = y;
   c = z;
}
};

int main(){
Foo f; //a gets 10, b gets 11, c gets 12
Foo fi(51, 52, 53); //a gets 51, b gets 52, c gets 53
}

1 Comment

"This is because in c++, execution is line by line.". By this you really mean "The compiler can optimise as it pleases so long as the resulting output, given any input, is identical to what would happen if the C++ execution is line by line." In the OP's case, the optimiser could produce int main(){}.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.