3

This is related to a question posted yesterday.

class A
{
public:
    mutable int x;
   A()  
   { 
      static int i = 0; 
      x = i; 
      i++;  
      std::cout << " A()" << std::endl; 
   }
   ~A() 
   { 
       std::cout << "~A()" << std::endl; 
   }
   void foo() const 
   { 
       x = 1; 
   };
};

class B
{
public:
   const A & a;
   B(const A & a) : a(a) 
   { 
       std::cout << " B()" << std::endl; 
   }
   ~B() 
   { 
       std::cout << "~B()" << std::endl; 
   }
   void doSomething() 
   { 
       a.foo(); 
   };
};

int main() 
{
   B b((A()));
   b.doSomething();
}

Now, a's destructor is called before the call to doSomething. However, the call works although the function basically changes a member of A. Is it not the same instance. No other A's are created. I used the static inside A's constructor to keep track of that. Can anyone explain?

Community
  • 1
  • 1
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625

4 Answers4

6

This is undefined behavior, so there is no language standard explanation.

However, the destructor of A doesn't do anything to the memory area where x is stored, so if you look there later the value might just still be there. Or if you try to write to the address, the address is still there. You are just not allowed to do that.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
0
  • Your reference is invalid after ~A() and it is undefined behavior
  • ~A() calls destructors of all members of A in addition

Try so for example

    class B
    {
    public:
            const std::string & a;
            B(const std::string & a) : a(a) 
            { 
                    std::cout << " B()" << std::endl; 
            }
            ~B() 
            { 
                    std::cout << "~B()" << std::endl; 
            }
            void doSomething() 
            { 
                    std::cout << "a = " << a << std::endl; 
            };
    };

    int main() 
    {
            B b(std::string("I love C++ so much!"));
            b.doSomething();
    }
tim
  • 788
  • 4
  • 14
0

Bo is correct.

In addition, you could check the address where 'A' is stored, and that should confirm that that address simply hasn't been reused yet (keep in mind a destructor frees ("releases") the memory, but doesn't traverse the data structure setting all of the bits back to 0; that would be inefficient).

If, for example, you find that A is stored on top of the stack, then you are simply fortunate that your subsequent function call doesn't pass in a parameter, as that would overwrite A's memory region.

GigaQuantum
  • 331
  • 2
  • 7
0

Expanding on Bo's answer.

For a temporary to exist, space will be reserved on the stack. This space is actually reserved as long as the semantics require the temporary to exist, and may then be reuse for something else.

If you were trying to use the memory after it has been reused, you would observe a strange behavior (the very definition of undefined behavior being that anything can happen). As it is, you luck out and the memory is still there, in the state you expect it to.

Example:

 #include <iostream>

 struct A {
   A(): p(0) {}
   ~A() { if (p) { std::cout << *p << "\n"; } }
   int* p;
 };

 int bar0();
 void bar1(int i);

 int main() {
   A a;
   {
     int x = 4; a.p = &x;
   }
   {
     int y = bar0(); bar1(y);
   }
 }

 int bar0() { return 7; }
 void bar1(int i) { std::cout << i << "\n"; }

Here, the compiler may choose to reuse the space of x for y, or just do anything it wants, and thus you're actually printing garbage.

Here is gcc 4.3.4 (and 4.5.1) output (courtesy of ideone):

7
4

Meaning that the space is not reused with those...

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722