1

Just stumbled on this, can anybody explain what is happening here?

struct Foo {
    int i;
    ~Foo() {
        std::cout << i << std::endl;
    }
};

void bar()
{
    Foo f;
    f.i = 1;
    f = Foo();
    f.i = 2;
}

I'm getting the following output:

-85899... (gibberish = "default" value for uninitialized int)
2

where I expected

1
2

Why is it that f.i = 1; seems to have no effect here?

Niko
  • 26,516
  • 9
  • 93
  • 110
  • 6
    Because the destructor for f doesn't get called when you assign a new Foo value to it. The first output is probably from the destructor of the temporary in f = Foo(). – Joris Timmermans May 10 '13 at 12:46
  • 3
    @mfontanini There is no undefined behavior. The value of `i` when not initialized is indeterminate. The behavior is defined but not determinate. Two different things entirely. – pmr May 10 '13 at 12:48
  • Are you using an ancient compiler? Since 2003 (or possibly even 1998), `Foo()` should value-initialise the temporary, setting `i` to `0`. – Mike Seymour May 10 '13 at 13:20
  • @MikeSeymour I'm using VC10. – Niko May 10 '13 at 13:28
  • 2
    @Niko: So it's buggy rather than ancient. [Here's the bug report](https://connect.microsoft.com/VisualStudio/feedback/details/746973/). – Mike Seymour May 10 '13 at 13:34

4 Answers4

11

So, the variable being destroyed in the first call to the destructor is not f, but the temporary created by Foo(). Since you don't have a constructor, i has an indeterminate value. If you were to add a constructor that sets i to, say, 99999, then you would see that output from your destructor.

void bar()
{
    Foo f;    // Construct f of type Foo
    f.i = 1;   // Set i to 1 in f. 
    f = Foo();   // Construct a temporary Foo object, copy it to f, 
                // then destroy the temporary object. 
    f.i = 2;   // Set the newly copied f.i to 2. 
               // destroy f.
}
Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
9

The f.i = 1 does have an effect. It sets the member i to be equal to 1. You'll only see that if the object is destroyed, since you output the value in the destructor.

In the line f = Foo();, you're creating a temporary Foo object, which has an i with indeterminate value and then assign it to the object f. This temporary object is destroyed at the end of the line, printing it its own indeterminate i. This is what gives you the -85899... output.

The indeterminate value is copied over to the object f, but then you overwrite its member i with the value 2. At the end of bar, this object is destroyed and you see the output 2.

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • 2
    I don't think there's any more that could possibly be said about the piece of code in the question! +1 – Joris Timmermans May 10 '13 at 12:50
  • 1
    It might be worth mentioning that a reasonably modern, non-buggy compiler should value-intialise the temporary and print zero rather than garbage. – Mike Seymour May 10 '13 at 17:19
  • @MikeSeymour C++ will never value-initialize member variables with simple types unless you tell it to. This is by design -- value-initializing them would take time, and C++ assumes that you don't care (and don't want to waste that time) unless you tell it otherwise. In particular, the value is considered "indeterminate". – Edward Loper May 13 '13 at 17:19
  • @EdwardLoper: No, by writing `Foo()`, you tell it to value-initialise the temporary (and thereby value-initialise its members). There's no way to create a default-initialised temporary. – Mike Seymour May 13 '13 at 17:25
  • @MikeSeymour Right you are -- I hadn't noticed that. For anyone else who's interested in the details of why Mike is right, see: http://stackoverflow.com/a/5999549/222329 – Edward Loper May 13 '13 at 18:18
4

this:

f = Foo();

creates a new object, and copy it. then this object got destoyed, at the end of this line, but it was not initialized at all. just copied.

Elazar
  • 20,415
  • 4
  • 46
  • 67
2

The first output is from the destructor called on the temporary. The default assignment operator does not call any destructor thus the one that would print 1 is never called.

Some code to illustrate that:

struct Foo {
  int i;
  // we emulate the default operator= generated by the compiler
  // no check for self-assignment
  Foo& operator=(const Foo& other) { this->i = other.i; } 
};

void bar() {
  F f;
  f.i = 1;
  f = Foo(); // the i member of the temporary is indeterminate
             // now f.i is indeterminate
             // destroy the temporary
  f.i = 2;  
} // end of scope, destroy f
pmr
  • 58,701
  • 10
  • 113
  • 156